From fcd027777d128ffe73144f9d4a1536533432aee3 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 15 May 2026 11:43:10 +1000 Subject: [PATCH 1/6] feat: add external plugin submission workflows Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/external-plugin.yml | 127 +++++ .../external-plugin-approval-command.yml | 528 ++++++++++++++++++ .github/workflows/external-plugin-intake.yml | 172 ++++++ .../external-plugin-rereview-command.yml | 308 ++++++++++ .../workflows/external-plugin-rereview.yml | 266 +++++++++ AGENTS.md | 13 +- CONTRIBUTING.md | 105 +++- eng/external-plugin-approval.mjs | 174 ++++++ eng/external-plugin-intake.mjs | 365 ++++++++++++ eng/external-plugin-rereview.mjs | 248 ++++++++ eng/external-plugin-validation.mjs | 368 ++++++++++++ eng/generate-marketplace.mjs | 93 +-- eng/validate-plugins.mjs | 19 +- 13 files changed, 2684 insertions(+), 102 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/external-plugin.yml create mode 100644 .github/workflows/external-plugin-approval-command.yml create mode 100644 .github/workflows/external-plugin-intake.yml create mode 100644 .github/workflows/external-plugin-rereview-command.yml create mode 100644 .github/workflows/external-plugin-rereview.yml create mode 100644 eng/external-plugin-approval.mjs create mode 100644 eng/external-plugin-intake.mjs create mode 100644 eng/external-plugin-rereview.mjs create mode 100644 eng/external-plugin-validation.mjs diff --git a/.github/ISSUE_TEMPLATE/external-plugin.yml b/.github/ISSUE_TEMPLATE/external-plugin.yml new file mode 100644 index 000000000..1a0f797eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/external-plugin.yml @@ -0,0 +1,127 @@ +name: External plugin submission +description: Submit a public GitHub-hosted external plugin for marketplace review. +title: "[External Plugin]: " +labels: + - external-plugin + - awaiting-review +body: + - type: markdown + attributes: + value: | + + Thanks for submitting a public external plugin. + + Before you continue: + - Public submissions are **GitHub-only** in v1. + - The plugin must live in a **public GitHub repository**. + - Use an **immutable ref** for review: a release tag or full 40-character commit SHA. + - Do **not** open a PR that edits `plugins/external.json` directly. + - type: input + id: plugin-name + attributes: + label: Plugin name + description: Lowercase letters, numbers, and hyphens only. + placeholder: my-plugin + validations: + required: true + - type: textarea + id: short-description + attributes: + label: Short description + description: One or two sentences describing the plugin. + placeholder: Helps developers... + validations: + required: true + - type: input + id: github-repository + attributes: + label: GitHub repository + description: Public GitHub repository in owner/repo format. + placeholder: owner/repo + validations: + required: true + - type: input + id: plugin-path + attributes: + label: Plugin path inside the repository + description: Optional if the plugin lives at the repository root. + placeholder: .github/plugins/my-plugin + validations: + required: false + - type: input + id: immutable-ref + attributes: + label: Immutable ref to review + description: Release tag or full 40-character commit SHA. + placeholder: refs/tags/v1.2.3 or 0123456789abcdef0123456789abcdef01234567 + validations: + required: true + - type: input + id: version + attributes: + label: Version + placeholder: 1.0.0 + validations: + required: true + - type: input + id: license + attributes: + label: License identifier + description: SPDX identifier or other license string. + placeholder: MIT + validations: + required: true + - type: input + id: author-name + attributes: + label: Author name + placeholder: Example Maintainers + validations: + required: true + - type: input + id: author-url + attributes: + label: Author URL + description: Optional HTTPS URL. + placeholder: https://example.com + validations: + required: false + - type: input + id: homepage-url + attributes: + label: Homepage URL + description: Optional HTTPS URL if different from the repository URL. + placeholder: https://example.com/plugin + validations: + required: false + - type: textarea + id: keywords + attributes: + label: Keywords + description: Comma-separated or newline-separated lowercase tags. + placeholder: | + automation + github + copilot + validations: + required: false + - type: textarea + id: additional-notes + attributes: + label: Additional notes for reviewers + description: Optional context that helps maintainers review the plugin. + validations: + required: false + - type: checkboxes + id: submission-checklist + attributes: + label: Submission checklist + options: + - label: The plugin lives in a public GitHub repository. + required: true + - label: The ref I provided is an immutable release tag or full 40-character commit SHA, not a branch. + required: true + - label: This submission follows this repository's contribution, security, and responsible AI policies. + required: true + - label: This plugin is not already listed in the Awesome Copilot marketplace. + required: true diff --git a/.github/workflows/external-plugin-approval-command.yml b/.github/workflows/external-plugin-approval-command.yml new file mode 100644 index 000000000..7b81c31f0 --- /dev/null +++ b/.github/workflows/external-plugin-approval-command.yml @@ -0,0 +1,528 @@ +name: External Plugin Approval Commands + +on: + issue_comment: + types: [created] + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + handle-command: + runs-on: ubuntu-latest + if: >- + !github.event.issue.pull_request && + (contains(github.event.comment.body, '/approve') || contains(github.event.comment.body, '/reject')) + steps: + - name: Checkout staged branch + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: staged + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22 + cache: npm + + - name: Parse decision command + id: parse + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const path = require('path'); + const { pathToFileURL } = require('url'); + + const approval = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-approval.mjs')).href); + const intake = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-intake.mjs')).href); + + const allowedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); + const parsedCommand = approval.parseDecisionCommand(context.payload.comment.body); + + core.setOutput('should-run', 'false'); + if (!parsedCommand) { + core.info('No supported external plugin approval command was found.'); + return; + } + + if (!allowedAssociations.has(context.payload.comment.author_association)) { + core.info(`Ignoring ${parsedCommand.command} because ${context.payload.comment.author_association} is not a maintainer association.`); + return; + } + + const currentIssue = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const labelNames = new Set((currentIssue.data.labels || []).map((label) => label.name)); + if (!labelNames.has('external-plugin')) { + core.info('Ignoring command because the issue is not an external plugin submission.'); + return; + } + + const evaluation = await intake.evaluateExternalPluginIssue({ + issue: currentIssue.data, + token: process.env.GITHUB_TOKEN + }); + + const fallbackName = evaluation.plugin?.name ?? `issue-${context.issue.number}`; + const canApprove = labelNames.has('ready-for-review') || labelNames.has('approved'); + const canReject = !labelNames.has('approved'); + + if (parsedCommand.command === 'approve' && !canApprove) { + core.info('Ignoring /approve because the issue is not ready for review.'); + return; + } + + if (parsedCommand.command === 'reject' && !canReject) { + core.info('Ignoring /reject because the issue is already approved.'); + return; + } + + core.setOutput('should-run', 'true'); + core.setOutput('command', parsedCommand.command); + core.setOutput('reason', parsedCommand.reason ?? ''); + core.setOutput('validation-valid', evaluation.valid ? 'true' : 'false'); + core.setOutput('validation-errors', JSON.stringify(evaluation.errors)); + core.setOutput('plugin-name', fallbackName); + core.setOutput('plugin-slug', approval.slugifyPluginName(fallbackName)); + core.setOutput('source-repo', evaluation.plugin?.source?.repo ?? ''); + + - name: Comment blocked approval + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'approve' && steps.parse.outputs.validation-valid != 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + VALIDATION_ERRORS: ${{ steps.parse.outputs.validation-errors }} + PLUGIN_NAME: ${{ steps.parse.outputs.plugin-name }} + with: + script: | + const marker = ''; + const errors = JSON.parse(process.env.VALIDATION_ERRORS || '[]'); + const body = [ + marker, + '## ⚠️ External plugin approval blocked', + '', + `The current issue form for **${process.env.PLUGIN_NAME}** no longer passes automated intake validation, so \`/approve\` was not applied.`, + '', + '### Required fixes', + '', + ...(errors.length > 0 ? errors.map((error) => `- ${error}`) : ['- Re-run intake validation by updating the issue details.']) + ].join('\n'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + 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: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + } + + - name: Install dependencies + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'approve' && steps.parse.outputs.validation-valid == 'true' + run: npm ci + + - name: Update external plugin catalog and PR + id: approval_pr + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'approve' && steps.parse.outputs.validation-valid == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + result=$(node ./eng/external-plugin-approval.mjs approve "$GITHUB_EVENT_PATH" --file ./plugins/external.json) + { + echo 'result<> "$GITHUB_OUTPUT" + + plugin_name=$(node -e "const data = JSON.parse(process.argv[1]); process.stdout.write(data.plugin.name);" "$result") + action=$(node -e "const data = JSON.parse(process.argv[1]); process.stdout.write(data.action);" "$result") + source_repo=$(node -e "const data = JSON.parse(process.argv[1]); process.stdout.write(data.plugin.source.repo);" "$result") + plugin_slug='${{ steps.parse.outputs.plugin-slug }}' + issue_number='${{ github.event.issue.number }}' + branch="automation/external-plugin-approve-${issue_number}-${plugin_slug}" + + if [ "$action" = "inserted" ]; then + title_action="Add" + summary_action="add" + else + title_action="Update" + summary_action="update" + fi + + npm run build + bash eng/fix-line-endings.sh + + pr_url="" + pr_number="" + if git diff --quiet; then + pr_number=$(gh pr list --head "$branch" --base staged --json number --jq '.[0].number') + if [ -n "$pr_number" ]; then + pr_url=$(gh pr view "$pr_number" --json url --jq '.url') + fi + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "plugin-name=$plugin_name" >> "$GITHUB_OUTPUT" + echo "action=$action" >> "$GITHUB_OUTPUT" + echo "source-repo=$source_repo" >> "$GITHUB_OUTPUT" + echo "pr-url=$pr_url" >> "$GITHUB_OUTPUT" + echo "pr-number=$pr_number" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B "$branch" + git add -A + git commit -m "${title_action} external plugin ${plugin_name}" + git push --force-with-lease origin "$branch" + + pr_number=$(gh pr list --head "$branch" --base staged --json number --jq '.[0].number') + pr_body=$(cat <> "$GITHUB_OUTPUT" + echo "plugin-name=$plugin_name" >> "$GITHUB_OUTPUT" + echo "action=$action" >> "$GITHUB_OUTPUT" + echo "source-repo=$source_repo" >> "$GITHUB_OUTPUT" + echo "pr-url=$pr_url" >> "$GITHUB_OUTPUT" + echo "pr-number=$pr_number" >> "$GITHUB_OUTPUT" + + - name: Finalize approval + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'approve' && steps.parse.outputs.validation-valid == 'true' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + CHANGED: ${{ steps.approval_pr.outputs.changed }} + ACTION: ${{ steps.approval_pr.outputs.action }} + PLUGIN_NAME: ${{ steps.approval_pr.outputs.plugin-name }} + SOURCE_REPO: ${{ steps.approval_pr.outputs.source-repo }} + PR_URL: ${{ steps.approval_pr.outputs.pr-url }} + PR_NUMBER: ${{ steps.approval_pr.outputs.pr-number }} + 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' + } + }; + + 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 removeLabel(issueNumber, name) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + name + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } + + async function syncIssueLabels(issueNumber, desiredLabels) { + await Promise.all(Object.entries(managedLabels).map(([name, config]) => ensureLabel(name, config))); + + 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) => Object.prototype.hasOwnProperty.call(managedLabels, 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 removeLabel(issueNumber, name); + } + } + + const issueNumber = context.issue.number; + const prNumber = Number(process.env.PR_NUMBER || 0); + const marker = ''; + const action = process.env.ACTION === 'updated' ? 'updated' : 'added'; + const prUrl = process.env.PR_URL; + const body = [ + marker, + '## ✅ External plugin approved', + '', + `A maintainer approved **${process.env.PLUGIN_NAME}**, and the submission issue has been closed.`, + '', + `- **Catalog action:** ${action}`, + `- **Source repository:** \`${process.env.SOURCE_REPO}\``, + prUrl + ? `- **PR against \`staged\`:** ${prUrl}` + : '- **PR against `staged`:** No new PR was needed because the approved listing is already present.' + ].join('\n'); + + await syncIssueLabels(issueNumber, new Set(['external-plugin', 'approved'])); + + if (prNumber > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + labels: ['external-plugin', 'awaiting-review'] + }); + } + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.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: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body + }); + } + + if (context.payload.issue.state !== 'closed') { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + state: 'closed' + }); + } + + - name: Finalize rejection + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'reject' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + REASON: ${{ steps.parse.outputs.reason }} + PLUGIN_NAME: ${{ steps.parse.outputs.plugin-name }} + 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' + } + }; + + 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 removeLabel(name) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } + + await Promise.all(Object.entries(managedLabels).map(([name, config]) => ensureLabel(name, config))); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['external-plugin', 'rejected'] + }); + + await removeLabel('awaiting-review'); + await removeLabel('ready-for-review'); + await removeLabel('approved'); + + const marker = ''; + const reason = process.env.REASON || 'No additional reason was provided.'; + const body = [ + marker, + '## ❌ External plugin rejected', + '', + `A maintainer rejected **${process.env.PLUGIN_NAME}**, and the submission issue has been closed.`, + '', + '### Reason', + '', + reason, + '', + 'If you address the feedback, open a new external plugin submission issue with the updated details.' + ].join('\n'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + 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: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + } + + if (context.payload.issue.state !== 'closed') { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + state: 'closed' + }); + } diff --git a/.github/workflows/external-plugin-intake.yml b/.github/workflows/external-plugin-intake.yml new file mode 100644 index 000000000..ce8d47d59 --- /dev/null +++ b/.github/workflows/external-plugin-intake.yml @@ -0,0 +1,172 @@ +name: External Plugin Intake + +on: + issues: + types: [opened, edited, reopened] + +permissions: + contents: read + issues: write + +jobs: + validate-submission: + runs-on: ubuntu-latest + if: >- + contains(github.event.issue.labels.*.name, 'external-plugin') || + contains(github.event.issue.body, '') + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Evaluate submission + id: evaluation + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + result=$(node ./eng/external-plugin-intake.mjs "$GITHUB_EVENT_PATH") + { + echo 'result<> "$GITHUB_OUTPUT" + + - name: Sync labels and comment + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + 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' + } + }; + + 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 result = JSON.parse(process.env.RESULT_JSON); + const issueNumber = context.issue.number; + const issueState = context.payload.issue.state; + const action = context.payload.action; + const existingLabelNames = (context.payload.issue.labels || []).map((label) => label.name); + + if (existingLabelNames.includes('approved')) { + core.info('Issue is already approved; skipping intake synchronization.'); + return; + } + + if (issueState === 'closed' && action !== 'reopened') { + core.info('Issue is closed; waiting for reopen before rerunning intake synchronization.'); + 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({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + per_page: 100 + }); + + 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, + repo: context.repo.repo, + issue_number: issueNumber, + state: 'closed' + }); + } diff --git a/.github/workflows/external-plugin-rereview-command.yml b/.github/workflows/external-plugin-rereview-command.yml new file mode 100644 index 000000000..f26a5a53f --- /dev/null +++ b/.github/workflows/external-plugin-rereview-command.yml @@ -0,0 +1,308 @@ +name: External Plugin Re-review Commands + +on: + issue_comment: + types: [created] + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + handle-command: + runs-on: ubuntu-latest + if: >- + !github.event.issue.pull_request && + contains(github.event.comment.body, '/re-review-') + steps: + - name: Checkout staged branch + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: staged + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22 + cache: npm + + - name: Parse re-review command + id: parse + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const path = require('path'); + const { pathToFileURL } = require('url'); + + const rereview = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-rereview.mjs')).href); + const validation = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-validation.mjs')).href); + + const allowedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); + const command = rereview.parseRereviewCommand(context.payload.comment.body); + + core.setOutput('should-run', 'false'); + if (!command) { + core.info('No supported re-review command was found.'); + return; + } + + if (!allowedAssociations.has(context.payload.comment.author_association)) { + core.info(`Ignoring ${command} because ${context.payload.comment.author_association} is not a maintainer association.`); + return; + } + + const labelNames = new Set((context.payload.issue.labels || []).map((label) => label.name)); + if (!labelNames.has('external-plugin') || !labelNames.has('approved')) { + core.info('Ignoring command because the issue is not an approved external plugin submission.'); + return; + } + + const { plugins, errors } = validation.readExternalPlugins({ policy: 'marketplace' }); + if (errors.length > 0) { + core.setFailed(errors.join('\n')); + return; + } + + const currentIssue = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const match = rereview.matchExternalPluginForIssue(currentIssue.data, plugins); + const plugin = match.plugin; + const fallbackName = match.submission.pluginName ?? `issue-${context.issue.number}`; + + core.setOutput('should-run', 'true'); + core.setOutput('command', command); + core.setOutput('has-plugin', plugin ? 'true' : 'false'); + core.setOutput('plugin-name', plugin?.name ?? fallbackName); + core.setOutput('plugin-slug', rereview.slugifyPluginName(plugin?.name ?? fallbackName)); + core.setOutput('source-repo', plugin?.source?.repo ?? match.submission.sourceRepo ?? ''); + + - name: Renew six-month review window + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'keep' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const pluginName = '${{ steps.parse.outputs.plugin-name }}'; + const hasPlugin = '${{ steps.parse.outputs.has-plugin }}' === 'true'; + + async function removeLabel(name) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } + + if (!hasPlugin) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `Could not find a current \`plugins/external.json\` entry for **${pluginName}**, so the six-month re-review window was not reset. Review the listing manually before retrying.` + }); + return; + } + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + state: 'open' + }); + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + state: 'closed' + }); + + await removeLabel('re-review-due'); + await removeLabel('re-review-follow-up'); + await removeLabel('removed'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `Renewed **${pluginName}** for another six months by reopening and reclosing this approved submission issue.` + }); + + - name: Mark follow-up needed + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'needs-changes' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const managedLabels = { + 're-review-due': { + color: 'FBCA04', + description: 'Approved external plugin is due for six-month re-review' + }, + 're-review-follow-up': { + color: 'D4C5F9', + description: 'Six-month re-review needs maintainer follow-up before a final decision' + } + }; + + 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; + } + } + } + + await Promise.all(Object.entries(managedLabels).map(([name, config]) => ensureLabel(name, config))); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['re-review-due', 're-review-follow-up'] + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `Marked **${{ steps.parse.outputs.plugin-name }}** as needing follow-up. The plugin will stay in the six-month re-review queue until a maintainer comments \`/re-review-keep\` or \`/re-review-remove\`.` + }); + + - name: Install dependencies + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'remove' && steps.parse.outputs.has-plugin == 'true' + run: npm ci + + - name: Remove plugin and create PR + id: remove_pr + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'remove' && steps.parse.outputs.has-plugin == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + plugin_name='${{ steps.parse.outputs.plugin-name }}' + plugin_slug='${{ steps.parse.outputs.plugin-slug }}' + source_repo='${{ steps.parse.outputs.source-repo }}' + issue_number='${{ github.event.issue.number }}' + branch="automation/external-plugin-rereview-remove-${issue_number}-${plugin_slug}" + + node ./eng/external-plugin-rereview.mjs remove --plugin-name "$plugin_name" --source-repo "$source_repo" --file ./plugins/external.json + npm run build + bash eng/fix-line-endings.sh + + if git diff --quiet; then + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B "$branch" + git add -A + git commit -m "Remove external plugin ${plugin_name} after six-month re-review" + git push --force-with-lease origin "$branch" + + pr_url=$(gh pr list --head "$branch" --base staged --json url --jq '.[0].url') + if [ -z "$pr_url" ]; then + pr_body=$(printf '%s\n' \ + '## Summary' \ + '' \ + "- remove \`${plugin_name}\` from \`plugins/external.json\`" \ + '- regenerate marketplace outputs after the six-month re-review decision' \ + "- closes #${issue_number} review follow-up for this listing") + pr_url=$(gh pr create \ + --base staged \ + --head "$branch" \ + --title "[external-plugin] Remove ${plugin_name} after re-review" \ + --body "$pr_body") + fi + + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "pr-url=$pr_url" >> "$GITHUB_OUTPUT" + + - name: Finalize removal + if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'remove' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + CHANGED: ${{ steps.remove_pr.outputs.changed }} + PR_URL: ${{ steps.remove_pr.outputs.pr-url }} + with: + script: | + async function ensureLabel(name, color, description) { + try { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name, + color, + description + }); + } catch (error) { + if (error.status !== 422) { + throw error; + } + } + } + + async function removeLabel(name) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } + + await ensureLabel('removed', 'B60205', 'External plugin was removed from the marketplace after re-review'); + await removeLabel('approved'); + await removeLabel('re-review-due'); + await removeLabel('re-review-follow-up'); + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['removed'] + }); + + const changed = process.env.CHANGED === 'true'; + const prUrl = process.env.PR_URL; + const pluginName = '${{ steps.parse.outputs.plugin-name }}'; + const hasPlugin = '${{ steps.parse.outputs.has-plugin }}' === 'true'; + + let body; + if (!hasPlugin || !changed) { + body = `Marked **${pluginName}** as removed. No new PR was needed because the listing is already absent from \`plugins/external.json\`.`; + } else { + body = `Marked **${pluginName}** as removed and opened the removal PR: ${prUrl}`; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); diff --git a/.github/workflows/external-plugin-rereview.yml b/.github/workflows/external-plugin-rereview.yml new file mode 100644 index 000000000..e9f6a2a74 --- /dev/null +++ b/.github/workflows/external-plugin-rereview.yml @@ -0,0 +1,266 @@ +name: External Plugin Re-review + +on: + schedule: + - cron: "23 4 * * *" + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + sync-rereview: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Sync six-month re-review queue + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const path = require('path'); + const { pathToFileURL } = require('url'); + + const rereview = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-rereview.mjs')).href); + const validation = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-validation.mjs')).href); + + const managedLabels = { + [rereview.REREVIEW_LABELS.due]: { + color: 'FBCA04', + description: 'Approved external plugin is due for six-month re-review' + }, + [rereview.REREVIEW_LABELS.followUp]: { + color: 'D4C5F9', + description: 'Six-month re-review needs maintainer follow-up before a final decision' + }, + [rereview.REREVIEW_LABELS.removed]: { + color: 'B60205', + description: 'External plugin was removed from the marketplace after re-review' + } + }; + + 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 removeLabel(issueNumber, label) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + name: label + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } + + async function addLabel(issueNumber, label) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + labels: [label] + }); + } + + function formatDate(dateValue) { + return new Date(dateValue).toISOString().slice(0, 10); + } + + function daysPastThreshold(closedAt, threshold) { + const diff = Date.parse(threshold.toISOString()) - Date.parse(closedAt); + return Math.max(0, Math.floor(Math.abs(diff) / (1000 * 60 * 60 * 24))); + } + + await Promise.all(Object.entries(managedLabels).map(([name, config]) => ensureLabel(name, config))); + + const { plugins, errors } = validation.readExternalPlugins({ policy: 'marketplace' }); + if (errors.length > 0) { + core.setFailed(errors.join('\n')); + return; + } + + const threshold = new Date(); + threshold.setUTCDate(threshold.getUTCDate() - 183); + + const approvedIssues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'closed', + labels: 'external-plugin,approved', + per_page: 100 + }); + + const issueRecords = approvedIssues + .filter((issue) => !issue.pull_request && issue.closed_at) + .map((issue) => { + const match = rereview.matchExternalPluginForIssue(issue, plugins); + return { + issue, + match + }; + }); + + const dueRecords = issueRecords.filter(({ issue, match }) => { + if (!match.plugin) { + return false; + } + + return Date.parse(issue.closed_at) <= threshold.getTime(); + }); + + const unmatchedDueRecords = issueRecords.filter(({ issue, match }) => { + if (match.plugin) { + return false; + } + + return Date.parse(issue.closed_at) <= threshold.getTime(); + }); + + for (const { issue, match } of issueRecords) { + const labelNames = new Set((issue.labels || []).map((label) => label.name)); + const shouldHaveDueLabel = dueRecords.some((record) => record.issue.number === issue.number); + + if (shouldHaveDueLabel && !labelNames.has(rereview.REREVIEW_LABELS.due)) { + await addLabel(issue.number, rereview.REREVIEW_LABELS.due); + } + + if (!shouldHaveDueLabel && labelNames.has(rereview.REREVIEW_LABELS.due)) { + await removeLabel(issue.number, rereview.REREVIEW_LABELS.due); + } + + if (shouldHaveDueLabel && match.plugin && labelNames.has(rereview.REREVIEW_LABELS.removed)) { + await removeLabel(issue.number, rereview.REREVIEW_LABELS.removed); + } + } + + const openIssues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + }); + + const existingTrackerIssues = openIssues + .filter((issue) => !issue.pull_request && issue.body?.includes(rereview.REREVIEW_REPORT_MARKER)) + .sort((left, right) => left.number - right.number); + + if (dueRecords.length === 0 && unmatchedDueRecords.length === 0) { + for (const tracker of existingTrackerIssues) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: tracker.number, + state: 'closed' + }); + } + + core.info('No external plugins are currently due for six-month re-review.'); + return; + } + + const dueRows = dueRecords + .sort((left, right) => Date.parse(left.issue.closed_at) - Date.parse(right.issue.closed_at)) + .map(({ issue, match }) => { + const labelNames = new Set((issue.labels || []).map((label) => label.name)); + const status = labelNames.has(rereview.REREVIEW_LABELS.followUp) ? 'Needs follow-up' : 'Awaiting decision'; + const repo = match.plugin.source?.repo ?? match.submission.sourceRepo ?? '_unknown_'; + return `| ${match.plugin.name} | ${match.plugin.version} | \`${repo}\` | #${issue.number} | ${formatDate(issue.closed_at)} | ${daysPastThreshold(issue.closed_at, threshold)} | ${status} |`; + }); + + const unmatchedRows = unmatchedDueRecords + .sort((left, right) => Date.parse(left.issue.closed_at) - Date.parse(right.issue.closed_at)) + .map(({ issue, match }) => { + const pluginName = match.submission.pluginName ?? '_unknown_'; + const repo = match.submission.sourceRepo ? `\`${match.submission.sourceRepo}\`` : '_unknown_'; + return `| #${issue.number} | ${pluginName} | ${repo} | ${formatDate(issue.closed_at)} |`; + }); + + const body = [ + rereview.REREVIEW_REPORT_MARKER, + '## 🔁 External plugin six-month re-review queue', + '', + 'The following approved external plugin submissions have reached the six-month re-review threshold.', + 'Review the linked plugin, then comment on the **original approved submission issue** with one of:', + '', + `- \`${rereview.REREVIEW_COMMANDS.keep}\` — renew the plugin for another six months`, + `- \`${rereview.REREVIEW_COMMANDS.needsChanges}\` — keep the plugin in the due queue while follow-up work happens`, + `- \`${rereview.REREVIEW_COMMANDS.remove}\` — open or update a PR against \`staged\` that removes the plugin from the marketplace`, + '', + `- **Threshold date used by this run:** ${formatDate(threshold.toISOString())}`, + '', + '### Plugins due now', + '', + dueRows.length > 0 + ? [ + '| Plugin | Version | Source repo | Submission issue | Closed at | Days past threshold | Status |', + '|---|---|---|---:|---|---:|---|', + ...dueRows + ].join('\n') + : '_No currently listed plugins are due right now._', + unmatchedRows.length > 0 + ? [ + '', + '### Approved issues without a current marketplace match', + '', + 'These closed approved issues are older than six months, but no matching entry was found in `plugins/external.json`. Review them manually if the listing was renamed or removed outside the re-review flow.', + '', + '| Submission issue | Parsed plugin name | Parsed repo | Closed at |', + '|---:|---|---|---|', + ...unmatchedRows + ].join('\n') + : '', + ].filter(Boolean).join('\n'); + + if (existingTrackerIssues.length > 0) { + const [primary, ...duplicates] = existingTrackerIssues; + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: primary.number, + title: '🔁 External Plugin Six-Month Review', + body, + labels: [rereview.REREVIEW_LABELS.due] + }); + + for (const duplicate of duplicates) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: duplicate.number, + state: 'closed' + }); + } + + core.info(`Updated re-review tracker issue #${primary.number}.`); + return; + } + + const created = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '🔁 External Plugin Six-Month Review', + body, + labels: [rereview.REREVIEW_LABELS.due] + }); + + core.info(`Created re-review tracker issue #${created.data.number}.`); diff --git a/AGENTS.md b/AGENTS.md index acb17e3b4..9132de5af 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -162,10 +162,15 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin: **For External Plugins:** -1. Edit `plugins/external.json` and add an entry with `name`, `source`, `description`, and `version` -2. The `source` field should be an object specifying a GitHub repo, git URL, npm package, or pip package (see [CONTRIBUTING.md](CONTRIBUTING.md#adding-external-plugins)) -3. Run `npm run build` to regenerate marketplace.json -4. Verify the external plugin appears in `.github/plugin/marketplace.json` +1. Do not open a direct PR that edits `plugins/external.json` for a public third-party plugin submission +2. Public external plugin submissions use the external plugin issue workflow documented in [CONTRIBUTING.md](CONTRIBUTING.md#adding-external-plugins) +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 ` 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` ### Testing Instructions diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index de6cf1bc3..c2f6ab099 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ To maintain a safe, responsible, and high-signal collection, we will **not accep - **Promote Harmful Content**: Guidance that could lead to the creation of harmful, discriminatory, or inappropriate content - **Circumvent Platform Policies**: Attempts to work around GitHub, Microsoft, or other platform terms of service - **Duplicate Existing Model Strengths Without Meaningful Uplift**: Submissions that mainly tell Copilot to do work frontier models already handle well (for example, generic TypeScript, HTML, or other broadly-supported coding tasks) without addressing a clear gap, specialized workflow, or domain-specific constraint. These contributions are often lower value for users and can introduce weaker or conflicting guidance than the model's default behavior. -- **Plugins from remote sources**: While the plugin design allows us to support plugins from other GitHub repos, or other Git endpoints, we are not accepting contributions that simply add plugins from external sources. Plugins from remote sources represent a security risk as we are unable to verify their content for the policies we enforce on this repository. This policy does not apply to repositories that are managed by Microsoft or GitHub. +- **Unreviewed remote-source plugins**: Do not open a pull request that directly adds a third-party plugin to `plugins/external.json`. Public external plugins must use the review workflow documented below. In v1, that workflow only accepts plugins hosted in public GitHub repositories; non-GitHub sources such as generic git URLs, npm packages, and pip packages are not accepted for public submissions. ## Quality Guidelines @@ -189,33 +189,106 @@ plugins/my-plugin-id/ #### Adding External Plugins -External plugins are plugins hosted outside this repository (e.g., in a GitHub repo, npm package, or git URL). They are listed in `plugins/external.json` and merged into the generated `marketplace.json` during build. +External plugins are plugins hosted outside this repository and listed in `plugins/external.json`. Public contributors should **not** open a PR that edits `plugins/external.json` directly. Instead, submit external plugins through the public review workflow below. -To add an external plugin, append an entry to `plugins/external.json` following the [Claude Code plugin marketplace spec](https://code.claude.com/docs/en/plugin-marketplaces#plugin-entries). Each entry requires `name`, `source`, `description`, and `version`: +> [!IMPORTANT] +> Public external plugin submissions are GitHub-only in v1. The submitted plugin must live in a public GitHub repository and use `source.source: "github"`. + +##### Submission fields + +The external plugin issue form will collect these fields: + +- Plugin name +- Short description +- GitHub repository in `owner/repo` format +- Plugin path inside the repository (optional when the plugin is at the repository root) +- Immutable ref to review (`ref`), using a release tag or full commit SHA rather than a branch +- Plugin version +- License identifier +- Author name +- Author URL (optional) +- Homepage URL (optional) +- Keywords/tags +- Additional notes for reviewers (optional) +- Confirmation checkboxes that the repository is public, the ref is immutable, the submission follows this repository's policies, and the plugin is not a duplicate listing + +The repository's canonical validation rules live in `eng/external-plugin-validation.mjs`. Build scripts already reuse the marketplace policy from that module, and later issue automation should call the stricter `publicSubmission` policy so the JSON contract and workflow checks stay aligned. + +For entries committed to `plugins/external.json`, the current marketplace validation requires: + +- `name`, `description`, and `version` +- `author.name` +- `repository` as an HTTPS GitHub URL +- `keywords` as lowercase hyphenated tags +- `source.source: "github"` plus `source.repo` in `owner/repo` format +- optional `source.path` values to stay relative to the repository root + +The future public-submission policy builds on those rules and also requires `license` plus an immutable `source.ref`. + +##### Review workflow + +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 ` 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 `, 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. + +##### Maintainer review responsibilities + +Maintainers are responsible for confirming that the submission: + +- Clearly fits the Awesome Copilot collection and adds value beyond existing listings +- Uses a public GitHub repository and an immutable ref that can be reviewed reliably +- Includes the metadata needed for `plugins/external.json` (`name`, `description`, `version`, `source`, and any supplied author/homepage/license/keyword fields) +- Does not obviously duplicate an existing marketplace entry +- Continues to meet this repository's content, security, and responsible AI policies + +##### Review cadence and label semantics + +- `external-plugin`: applied to every public external plugin submission and retained on approved issues so scheduled review automation can find them later +- `awaiting-review`: initial intake state before automation finishes validating the issue +- `ready-for-review`: the issue passed automated intake checks and is waiting on a maintainer decision +- `approved`: the issue was approved, closed, and can be used as the source of truth for six-month re-review +- `rejected`: the issue was rejected and closed without being added to the marketplace +- `re-review-due`: the approved issue reached the six-month review threshold and is waiting on a maintainer re-review decision +- `re-review-follow-up`: a maintainer reviewed the plugin and requested more follow-up before renewing or removing it +- `removed`: the plugin was removed from `plugins/external.json` after re-review and should no longer be considered active + +The six-month re-review window starts when an approved submission issue is **closed**. A nightly workflow looks for closed issues labeled `external-plugin` and `approved` whose `closed_at` is at least six months old, applies `re-review-due`, and opens or updates a maintainer-facing tracking issue that links every plugin currently due. + +Maintainers complete the re-review on the **original approved submission issue** with one of these issue-comment commands: + +- `/re-review-keep` — renew the listing for another six months by reopening and reclosing the approved issue, which resets the `closed_at` review anchor and removes the due labels +- `/re-review-needs-changes` — keep the listing in the due queue while adding `re-review-follow-up` so maintainers can track extra investigation or remediation work +- `/re-review-remove` — open or update a PR against `staged` that removes the plugin from `plugins/external.json`, regenerates marketplace outputs, and relabels the issue as `removed` + +Approved submissions are converted into `plugins/external.json` entries following the [Claude Code plugin marketplace spec](https://code.claude.com/docs/en/plugin-marketplaces#plugin-entries). A typical GitHub-hosted entry looks like this: ```json [ { "name": "my-external-plugin", + "description": "Description of the external plugin", + "version": "1.0.0", + "author": { + "name": "Plugin Author", + "url": "https://github.com/plugin-author" + }, + "homepage": "https://github.com/owner/plugin-repo", + "keywords": ["category", "workflow"], + "license": "MIT", + "repository": "https://github.com/owner/plugin-repo", "source": { "source": "github", - "repo": "owner/plugin-repo" - }, - "description": "Description of the external plugin", - "version": "1.0.0" + "repo": "owner/plugin-repo", + "path": ".github/plugins/my-external-plugin", + "ref": "v1.0.0" + } } ] ``` -Supported source types: - -- **GitHub**: `{ "source": "github", "repo": "owner/repo", "ref": "v1.0.0" }` -- **Git URL**: `{ "source": "url", "url": "https://gitlab.com/team/plugin.git" }` -- **npm**: `{ "source": "npm", "package": "@scope/package", "version": "1.0.0" }` -- **pip**: `{ "source": "pip", "package": "package-name", "version": "1.0.0" }` - -After editing `plugins/external.json`, run `npm run build` to regenerate `marketplace.json`. - ### Adding Hooks Hooks enable automated workflows triggered by specific events during GitHub Copilot coding agent sessions, such as session start, session end, user prompts, and tool usage. diff --git a/eng/external-plugin-approval.mjs b/eng/external-plugin-approval.mjs new file mode 100644 index 000000000..32199d740 --- /dev/null +++ b/eng/external-plugin-approval.mjs @@ -0,0 +1,174 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { ROOT_FOLDER } from "./constants.mjs"; +import { + EXTERNAL_PLUGINS_FILE, + readExternalPlugins, + validateExternalPlugins, +} from "./external-plugin-validation.mjs"; +import { evaluateExternalPluginIssue } from "./external-plugin-intake.mjs"; + +export const DECISION_COMMANDS = Object.freeze({ + approve: "/approve", + reject: "/reject", +}); + +function normalizeValue(value) { + return String(value ?? "").trim().toLowerCase(); +} + +function normalizeRepositoryUrl(value) { + const normalized = normalizeValue(value); + if (!normalized) { + return undefined; + } + + return normalized + .replace(/^https:\/\/github\.com\//, "") + .replace(/\.git$/i, "") + .replace(/^\/+|\/+$/g, ""); +} + +export function parseDecisionCommand(body) { + const match = String(body ?? "").match(/(?:^|\n)\s*\/(approve|reject)\b([\s\S]*)$/i); + if (!match) { + return undefined; + } + + const command = match[1].toLowerCase(); + const reason = match[2]?.trim() || undefined; + + return { + command, + reason: command === "reject" ? reason : undefined, + }; +} + +export function slugifyPluginName(value) { + const slug = String(value ?? "") + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + + return slug || "external-plugin"; +} + +function readLocalPluginNames() { + const pluginsDir = path.join(ROOT_FOLDER, "plugins"); + if (!fs.existsSync(pluginsDir)) { + return []; + } + + return fs.readdirSync(pluginsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name); +} + +function pluginsMatch(left, right) { + const leftName = normalizeValue(left?.name); + const rightName = normalizeValue(right?.name); + const leftRepo = normalizeValue(left?.source?.repo); + const rightRepo = normalizeValue(right?.source?.repo); + const leftRepository = normalizeRepositoryUrl(left?.repository); + const rightRepository = normalizeRepositoryUrl(right?.repository); + + if (leftName && rightName && leftName === rightName) { + return true; + } + + if (leftRepo && rightRepo && leftRepo === rightRepo) { + return true; + } + + return Boolean(leftRepository && rightRepository && leftRepository === rightRepository); +} + +export function upsertExternalPlugin(plugin, { filePath = EXTERNAL_PLUGINS_FILE } = {}) { + const { plugins, errors } = readExternalPlugins({ + filePath, + localPluginNames: readLocalPluginNames(), + policy: "marketplace", + }); + + if (errors.length > 0) { + throw new Error(errors.join("\n")); + } + + const updatedPlugins = [...plugins]; + const existingIndex = updatedPlugins.findIndex((existingPlugin) => pluginsMatch(existingPlugin, plugin)); + const action = existingIndex === -1 ? "inserted" : "updated"; + + if (existingIndex === -1) { + updatedPlugins.push(plugin); + } else { + updatedPlugins[existingIndex] = plugin; + } + + updatedPlugins.sort((left, right) => left.name.localeCompare(right.name, undefined, { sensitivity: "base" })); + + const { errors: validationErrors } = validateExternalPlugins(updatedPlugins, { + localPluginNames: readLocalPluginNames(), + policy: "marketplace", + }); + + if (validationErrors.length > 0) { + throw new Error(validationErrors.join("\n")); + } + + const changed = JSON.stringify(updatedPlugins) !== JSON.stringify(plugins); + if (changed) { + fs.writeFileSync(filePath, `${JSON.stringify(updatedPlugins, null, 2)}\n`); + } + + return { + action, + changed, + plugin, + }; +} + +function readCliArgs(argv) { + const args = {}; + + for (let index = 0; index < argv.length; index += 1) { + const key = argv[index]; + if (!key.startsWith("--")) { + continue; + } + + args[key.slice(2)] = argv[index + 1]; + index += 1; + } + + return args; +} + +const isCli = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); + +if (isCli) { + const [command, eventPath] = process.argv.slice(2); + + if (command !== "approve" || !eventPath) { + console.error("Usage: node ./eng/external-plugin-approval.mjs approve [--file ]"); + process.exit(1); + } + + const args = readCliArgs(process.argv.slice(4)); + const event = JSON.parse(fs.readFileSync(eventPath, "utf8")); + const evaluation = await evaluateExternalPluginIssue({ + issue: event.issue, + token: process.env.GITHUB_TOKEN, + }); + + if (!evaluation.valid) { + console.error(evaluation.errors.join("\n")); + process.exit(1); + } + + const result = upsertExternalPlugin(evaluation.plugin, { filePath: args.file }); + process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); +} diff --git a/eng/external-plugin-intake.mjs b/eng/external-plugin-intake.mjs new file mode 100644 index 000000000..f1e3da9b9 --- /dev/null +++ b/eng/external-plugin-intake.mjs @@ -0,0 +1,365 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { ROOT_FOLDER } from "./constants.mjs"; +import { readExternalPlugins, validateExternalPlugin } from "./external-plugin-validation.mjs"; + +const ISSUE_FORM_MARKER = ""; +const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins"); + +const REQUIRED_CHECKLIST_ITEMS = [ + "The plugin lives in a public GitHub repository.", + "The ref I provided is an immutable release tag or full 40-character commit SHA, not a branch.", + "This submission follows this repository's contribution, security, and responsible AI policies.", + "This plugin is not already listed in the Awesome Copilot marketplace.", +]; + +const FIELD_TITLES = Object.freeze({ + pluginName: "Plugin name", + shortDescription: "Short description", + githubRepository: "GitHub repository", + pluginPath: "Plugin path inside the repository", + immutableRef: "Immutable ref to review", + version: "Version", + license: "License identifier", + authorName: "Author name", + authorUrl: "Author URL", + homepageUrl: "Homepage URL", + keywords: "Keywords", + additionalNotes: "Additional notes for reviewers", + submissionChecklist: "Submission checklist", +}); + +function normalizeMultilineText(value) { + return String(value ?? "").replace(/\r\n/g, "\n"); +} + +function stripNoResponse(value) { + if (value === undefined) { + return undefined; + } + + const normalized = normalizeMultilineText(value).trim(); + if (!normalized || normalized === "_No response_") { + return undefined; + } + + return normalized; +} + +function parseIssueFormSections(body) { + const normalized = normalizeMultilineText(body); + const sections = new Map(); + const matches = [...normalized.matchAll(/^###\s+(.+)$/gm)]; + + for (let index = 0; index < matches.length; index += 1) { + const heading = matches[index][1].trim(); + const start = matches[index].index + matches[index][0].length; + const end = index + 1 < matches.length ? matches[index + 1].index : normalized.length; + const content = normalized.slice(start, end).trim(); + sections.set(heading, content); + } + + return sections; +} + +function normalizeGitHubRepo(value) { + if (!value) { + return undefined; + } + + const trimmed = value.trim(); + const urlMatch = trimmed.match(/^https:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?\/?$/i); + if (urlMatch) { + return urlMatch[1]; + } + + return trimmed.replace(/^github\.com\//i, "").replace(/\.git$/i, "").replace(/^\/+|\/+$/g, ""); +} + +function parseKeywords(value) { + const normalized = stripNoResponse(value); + if (!normalized) { + return undefined; + } + + const keywords = normalized + .split(/[\n,]/) + .map((entry) => entry.trim()) + .filter(Boolean); + + return keywords.length > 0 ? keywords : undefined; +} + +function parseChecklist(value) { + const checked = new Set(); + const normalized = normalizeMultilineText(value); + + for (const match of normalized.matchAll(/^- \[(x|X)\] (.+)$/gm)) { + checked.add(match[2].trim()); + } + + return checked; +} + +function readLocalPluginNames() { + if (!fs.existsSync(PLUGINS_DIR)) { + return []; + } + + return fs.readdirSync(PLUGINS_DIR, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name); +} + +function toSubmissionError(message) { + return message.replace(/^external\.json\[0\]:\s*/, "submission: "); +} + +async function fetchGitHubJson(apiPath, token) { + const response = await fetch(`https://api.github.com${apiPath}`, { + headers: { + Accept: "application/vnd.github+json", + "User-Agent": "awesome-copilot-external-plugin-intake", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }); + + if (response.status === 404) { + return { ok: false, status: 404, data: null }; + } + + let data = null; + try { + data = await response.json(); + } catch { + data = null; + } + + return { + ok: response.ok, + status: response.status, + data, + }; +} + +function encodeRepoPath(repo) { + const [owner, name] = String(repo).split("/"); + return `${encodeURIComponent(owner ?? "")}/${encodeURIComponent(name ?? "")}`; +} + +async function validateRemoteRepository(repo, ref, errors, warnings, token) { + const encodedRepo = encodeRepoPath(repo); + const repositoryResponse = await fetchGitHubJson(`/repos/${encodedRepo}`, token); + + if (!repositoryResponse.ok) { + if (repositoryResponse.status === 404) { + errors.push(`submission: GitHub repository "${repo}" was not found`); + } else { + errors.push(`submission: could not inspect GitHub repository "${repo}" (HTTP ${repositoryResponse.status})`); + } + return; + } + + if (repositoryResponse.data?.private) { + errors.push(`submission: GitHub repository "${repo}" must be public`); + } + + if (repositoryResponse.data?.archived) { + warnings.push(`submission: GitHub repository "${repo}" is archived`); + } + + if (!ref) { + return; + } + + if (/^[0-9a-f]+$/i.test(ref) && ref.length !== 40) { + errors.push('submission: commit SHAs in "Immutable ref to review" must use the full 40-character SHA'); + return; + } + + if (/^[0-9a-f]{40}$/i.test(ref)) { + const commitResponse = await fetchGitHubJson(`/repos/${encodedRepo}/commits/${encodeURIComponent(ref)}`, token); + if (!commitResponse.ok) { + errors.push(`submission: commit "${ref}" was not found in GitHub repository "${repo}"`); + } + return; + } + + const tagName = ref.startsWith("refs/tags/") ? ref.slice("refs/tags/".length) : ref; + const tagResponse = await fetchGitHubJson(`/repos/${encodedRepo}/git/ref/tags/${encodeURIComponent(tagName)}`, token); + + if (!tagResponse.ok) { + errors.push(`submission: tag "${ref}" was not found in GitHub repository "${repo}"`); + } +} + +export function parseExternalPluginIssueBody(body) { + const sections = parseIssueFormSections(body); + const errors = []; + + function requiredField(title) { + const value = stripNoResponse(sections.get(title)); + if (!value) { + errors.push(`submission: "${title}" is required`); + } + return value; + } + + const pluginName = requiredField(FIELD_TITLES.pluginName); + const shortDescription = requiredField(FIELD_TITLES.shortDescription); + const repoInput = normalizeGitHubRepo(requiredField(FIELD_TITLES.githubRepository)); + const immutableRef = requiredField(FIELD_TITLES.immutableRef); + const version = requiredField(FIELD_TITLES.version); + const license = requiredField(FIELD_TITLES.license); + const authorName = requiredField(FIELD_TITLES.authorName); + + const pluginPath = stripNoResponse(sections.get(FIELD_TITLES.pluginPath)); + const authorUrl = stripNoResponse(sections.get(FIELD_TITLES.authorUrl)); + const homepageUrl = stripNoResponse(sections.get(FIELD_TITLES.homepageUrl)); + const keywords = parseKeywords(sections.get(FIELD_TITLES.keywords)); + const additionalNotes = stripNoResponse(sections.get(FIELD_TITLES.additionalNotes)); + const checkedItems = parseChecklist(sections.get(FIELD_TITLES.submissionChecklist)); + + for (const item of REQUIRED_CHECKLIST_ITEMS) { + if (!checkedItems.has(item)) { + errors.push(`submission: checklist item must be checked: "${item}"`); + } + } + + const plugin = { + name: pluginName, + description: shortDescription, + version, + author: { + name: authorName, + ...(authorUrl ? { url: authorUrl } : {}), + }, + repository: repoInput ? `https://github.com/${repoInput}` : undefined, + ...(homepageUrl ? { homepage: homepageUrl } : {}), + ...(license ? { license } : {}), + ...(keywords ? { keywords } : {}), + source: { + source: "github", + repo: repoInput, + ...(pluginPath ? { path: pluginPath } : {}), + ...(immutableRef ? { ref: immutableRef } : {}), + }, + }; + + return { + markerPresent: normalizeMultilineText(body).includes(ISSUE_FORM_MARKER), + errors, + plugin, + additionalNotes, + }; +} + +export async function evaluateExternalPluginIssue({ issue, token } = {}) { + const issueBody = issue?.body ?? ""; + const parsed = parseExternalPluginIssueBody(issueBody); + const errors = [...parsed.errors]; + const warnings = []; + + const localPluginNames = readLocalPluginNames(); + const { plugins: existingExternalPlugins } = readExternalPlugins({ policy: "marketplace" }); + const duplicateNames = [ + ...localPluginNames, + ...existingExternalPlugins.map((plugin) => plugin.name).filter(Boolean), + ]; + + const validationResult = validateExternalPlugin(parsed.plugin, 0, { policy: "publicSubmission" }); + errors.push(...validationResult.errors.map(toSubmissionError)); + warnings.push(...validationResult.warnings.map(toSubmissionError)); + + if (parsed.plugin?.name) { + const matchingName = duplicateNames.find( + (name) => String(name).toLowerCase() === String(parsed.plugin.name).toLowerCase(), + ); + if (matchingName) { + errors.push(`submission: plugin name "${parsed.plugin.name}" conflicts with existing plugin "${matchingName}"`); + } + } + + if (parsed.plugin?.source?.repo && parsed.plugin?.source?.ref) { + await validateRemoteRepository(parsed.plugin.source.repo, parsed.plugin.source.ref, errors, warnings, token); + } + + const dedupedErrors = [...new Set(errors)]; + const dedupedWarnings = [...new Set(warnings)]; + const valid = dedupedErrors.length === 0; + const 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 + ? [ + "```json", + JSON.stringify(parsed.plugin, null, 2), + "```", + ].join("\n") + : "```json\n{}\n```"; + + const commentBody = valid + ? [ + marker, + "## ✅ External plugin intake passed", + "", + `This submission passed automated intake validation and is ready for maintainer review.`, + "", + `- **Plugin:** ${parsed.plugin.name}`, + `- **Repository:** ${parsed.plugin.repository}`, + `- **Ref:** ${parsed.plugin.source.ref}`, + `- **Keywords:** ${normalizedKeywords}`, + "", + "### Canonical external.json payload", + "", + payload, + "", + "### Reviewer notes", + "", + notes, + dedupedWarnings.length > 0 + ? ["", "### Warnings", "", ...dedupedWarnings.map((warning) => `- ${warning}`)].join("\n") + : "", + ].filter(Boolean).join("\n") + : [ + marker, + "## ❌ 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.", + "", + "### Required fixes", + "", + ...dedupedErrors.map((error) => `- ${error}`), + dedupedWarnings.length > 0 + ? ["", "### Warnings", "", ...dedupedWarnings.map((warning) => `- ${warning}`)].join("\n") + : "", + ].filter(Boolean).join("\n"); + + return { + valid, + markerPresent: parsed.markerPresent, + errors: dedupedErrors, + warnings: dedupedWarnings, + plugin: parsed.plugin, + commentBody, + commentMarker: marker, + }; +} + +const isCli = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); + +if (isCli) { + const eventPath = process.argv[2]; + if (!eventPath) { + console.error("Usage: node ./eng/external-plugin-intake.mjs "); + process.exit(1); + } + + const event = JSON.parse(fs.readFileSync(eventPath, "utf8")); + const result = await evaluateExternalPluginIssue({ issue: event.issue, token: process.env.GITHUB_TOKEN }); + process.stdout.write(JSON.stringify(result)); +} diff --git a/eng/external-plugin-rereview.mjs b/eng/external-plugin-rereview.mjs new file mode 100644 index 000000000..9790bc442 --- /dev/null +++ b/eng/external-plugin-rereview.mjs @@ -0,0 +1,248 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { EXTERNAL_PLUGINS_FILE, readExternalPlugins } from "./external-plugin-validation.mjs"; +import { parseExternalPluginIssueBody } from "./external-plugin-intake.mjs"; + +export const REREVIEW_REPORT_MARKER = ""; + +export const REREVIEW_LABELS = Object.freeze({ + due: "re-review-due", + followUp: "re-review-follow-up", + removed: "removed", +}); + +export const REREVIEW_COMMANDS = Object.freeze({ + keep: "/re-review-keep", + needsChanges: "/re-review-needs-changes", + remove: "/re-review-remove", +}); + +function normalizeValue(value) { + return String(value ?? "").trim().toLowerCase(); +} + +function normalizeRepositoryUrl(value) { + const normalized = normalizeValue(value); + if (!normalized) { + return undefined; + } + + return normalized + .replace(/^https:\/\/github\.com\//, "") + .replace(/\.git$/i, "") + .replace(/^\/+|\/+$/g, ""); +} + +function stripIssueTitlePrefix(title) { + return String(title ?? "") + .trim() + .replace(/^(external plugin(?: submission)?|public external plugin)(?:\s*[:-]\s*|\s+)/i, "") + .trim(); +} + +function firstMatch(body, patterns) { + for (const pattern of patterns) { + const match = body.match(pattern); + if (match?.[1]) { + return match[1].trim(); + } + } + + return undefined; +} + +function fallbackSubmissionData(issue) { + const body = String(issue?.body ?? ""); + const title = stripIssueTitlePrefix(issue?.title); + const sourceRepo = firstMatch(body, [ + /https:\/\/github\.com\/([^/\s]+\/[^/\s)]+)/i, + /\b([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\b/, + ]); + + return { + pluginName: title || undefined, + sourceRepo: sourceRepo ? normalizeRepositoryUrl(sourceRepo) : undefined, + repository: sourceRepo ? `https://github.com/${normalizeRepositoryUrl(sourceRepo)}` : undefined, + }; +} + +export function extractSubmissionData(issue) { + const parsed = parseExternalPluginIssueBody(issue?.body ?? ""); + const fallback = fallbackSubmissionData(issue); + const plugin = parsed.plugin ?? {}; + + return { + pluginName: plugin.name ?? fallback.pluginName, + sourceRepo: plugin.source?.repo ?? fallback.sourceRepo, + repository: plugin.repository ?? fallback.repository, + ref: plugin.source?.ref, + }; +} + +function pluginMatchesSubmission(plugin, submission) { + const pluginName = normalizeValue(plugin?.name); + const submissionName = normalizeValue(submission.pluginName); + const pluginRepo = normalizeValue(plugin?.source?.repo); + const submissionRepo = normalizeValue(submission.sourceRepo); + const pluginRepository = normalizeRepositoryUrl(plugin?.repository); + const submissionRepository = normalizeRepositoryUrl(submission.repository); + + const nameMatch = pluginName && submissionName && pluginName === submissionName; + const repoMatch = pluginRepo && submissionRepo && pluginRepo === submissionRepo; + const repositoryMatch = pluginRepository && submissionRepository && pluginRepository === submissionRepository; + + if (nameMatch && (repoMatch || repositoryMatch || !submissionRepo)) { + return true; + } + + if ((repoMatch || repositoryMatch) && (!submissionName || nameMatch)) { + return true; + } + + return false; +} + +export function matchExternalPluginForIssue(issue, plugins) { + const submission = extractSubmissionData(issue); + const exactMatch = plugins.find((plugin) => pluginMatchesSubmission(plugin, submission)); + if (exactMatch) { + return { + plugin: exactMatch, + submission, + matchReason: "exact", + }; + } + + const byRepo = submission.sourceRepo + ? plugins.find((plugin) => normalizeValue(plugin?.source?.repo) === normalizeValue(submission.sourceRepo)) + : undefined; + if (byRepo) { + return { + plugin: byRepo, + submission, + matchReason: "repo", + }; + } + + const byName = submission.pluginName + ? plugins.find((plugin) => normalizeValue(plugin?.name) === normalizeValue(submission.pluginName)) + : undefined; + if (byName) { + return { + plugin: byName, + submission, + matchReason: "name", + }; + } + + return { + plugin: undefined, + submission, + matchReason: "none", + }; +} + +export function parseRereviewCommand(body) { + const match = String(body ?? "").match(/(?:^|\n)\s*\/re-review-(keep|needs-changes|remove)\b/i); + if (!match) { + return undefined; + } + + switch (match[1].toLowerCase()) { + case "keep": + return "keep"; + case "needs-changes": + return "needs-changes"; + case "remove": + return "remove"; + default: + return undefined; + } +} + +export function slugifyPluginName(value) { + const slug = String(value ?? "") + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + + return slug || "external-plugin"; +} + +export function removePluginFromExternalJson({ pluginName, sourceRepo, filePath = EXTERNAL_PLUGINS_FILE } = {}) { + const { plugins, errors } = readExternalPlugins({ filePath, policy: "marketplace" }); + if (errors.length > 0) { + throw new Error(errors.join("\n")); + } + + const normalizedPluginName = normalizeValue(pluginName); + const normalizedSourceRepo = normalizeValue(sourceRepo); + const matchIndex = plugins.findIndex((plugin) => { + if (normalizedPluginName && normalizeValue(plugin?.name) === normalizedPluginName) { + return true; + } + + if (normalizedSourceRepo && normalizeValue(plugin?.source?.repo) === normalizedSourceRepo) { + return true; + } + + return false; + }); + + if (matchIndex === -1) { + throw new Error(`Could not find external plugin "${pluginName || sourceRepo}" in ${path.relative(process.cwd(), filePath)}`); + } + + const updatedPlugins = [...plugins]; + const [removedPlugin] = updatedPlugins.splice(matchIndex, 1); + fs.writeFileSync(filePath, `${JSON.stringify(updatedPlugins, null, 2)}\n`); + + return removedPlugin; +} + +function readCliArgs(argv) { + const args = {}; + + for (let index = 0; index < argv.length; index += 1) { + const key = argv[index]; + if (!key.startsWith("--")) { + continue; + } + + const value = argv[index + 1]; + args[key.slice(2)] = value; + index += 1; + } + + return args; +} + +const isCli = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]); + +if (isCli) { + const [command] = process.argv.slice(2); + + if (command !== "remove") { + console.error("Usage: node ./eng/external-plugin-rereview.mjs remove --plugin-name [--source-repo ] [--file ]"); + process.exit(1); + } + + const args = readCliArgs(process.argv.slice(3)); + + if (!args["plugin-name"] && !args["source-repo"]) { + console.error("Provide --plugin-name or --source-repo when removing an external plugin."); + process.exit(1); + } + + const removedPlugin = removePluginFromExternalJson({ + pluginName: args["plugin-name"], + sourceRepo: args["source-repo"], + filePath: args.file, + }); + + process.stdout.write(`${JSON.stringify(removedPlugin, null, 2)}\n`); +} diff --git a/eng/external-plugin-validation.mjs b/eng/external-plugin-validation.mjs new file mode 100644 index 000000000..282293a25 --- /dev/null +++ b/eng/external-plugin-validation.mjs @@ -0,0 +1,368 @@ +import fs from "fs"; +import path from "path"; +import { ROOT_FOLDER } from "./constants.mjs"; + +export const EXTERNAL_PLUGINS_FILE = path.join(ROOT_FOLDER, "plugins", "external.json"); + +export const EXTERNAL_PLUGIN_POLICIES = Object.freeze({ + marketplace: Object.freeze({ + allowedSourceTypes: ["github"], + requireAuthor: true, + requireRepository: true, + requireKeywords: true, + requireLicense: false, + requireImmutableRef: false, + }), + publicSubmission: Object.freeze({ + allowedSourceTypes: ["github"], + requireAuthor: true, + requireRepository: true, + requireKeywords: true, + requireLicense: true, + requireImmutableRef: true, + }), +}); + +function resolvePolicy(policy) { + if (!policy) { + return EXTERNAL_PLUGIN_POLICIES.marketplace; + } + + if (typeof policy === "string") { + return EXTERNAL_PLUGIN_POLICIES[policy] ?? EXTERNAL_PLUGIN_POLICIES.marketplace; + } + + return { + ...EXTERNAL_PLUGIN_POLICIES.marketplace, + ...policy, + }; +} + +function isNonEmptyString(value) { + return typeof value === "string" && value.trim().length > 0; +} + +function validatePluginName(name, prefix, errors) { + if (!isNonEmptyString(name)) { + errors.push(`${prefix}: "name" is required and must be a non-empty string`); + return; + } + + if (name.length > 50) { + errors.push(`${prefix}: "name" must be 50 characters or fewer`); + } + + if (!/^[a-z0-9-]+$/.test(name)) { + errors.push(`${prefix}: "name" must contain only lowercase letters, numbers, and hyphens`); + } +} + +function validateDescription(description, prefix, errors) { + if (!isNonEmptyString(description)) { + errors.push(`${prefix}: "description" is required and must be a non-empty string`); + return; + } + + if (description.length > 500) { + errors.push(`${prefix}: "description" must be 500 characters or fewer`); + } +} + +function validateVersion(version, prefix, errors) { + if (!isNonEmptyString(version)) { + errors.push(`${prefix}: "version" is required and must be a non-empty string`); + return; + } + + if (version.length > 100) { + errors.push(`${prefix}: "version" must be 100 characters or fewer`); + } +} + +function validateKeywords(keywords, prefix, errors, warnings, required) { + if (keywords === undefined) { + if (required) { + errors.push(`${prefix}: "keywords" is required and must be an array of lowercase tags`); + } + return; + } + + if (!Array.isArray(keywords)) { + errors.push(`${prefix}: "keywords" must be an array`); + return; + } + + if (keywords.length > 10) { + errors.push(`${prefix}: "keywords" must contain no more than 10 entries`); + } + + for (let i = 0; i < keywords.length; i++) { + const keyword = keywords[i]; + if (!isNonEmptyString(keyword)) { + errors.push(`${prefix}: "keywords[${i}]" must be a non-empty string`); + continue; + } + + if (!/^[a-z0-9-]+$/.test(keyword)) { + errors.push(`${prefix}: "keywords[${i}]" must contain only lowercase letters, numbers, and hyphens`); + } + + if (keyword.length > 30) { + errors.push(`${prefix}: "keywords[${i}]" must be 30 characters or fewer`); + } + } + + if (keywords.length === 0) { + warnings.push(`${prefix}: "keywords" is empty; at least one keyword is recommended for discovery`); + } +} + +function validateHttpsUrl(value, fieldName, prefix, errors, options = {}) { + if (!isNonEmptyString(value)) { + errors.push(`${prefix}: "${fieldName}" must be a non-empty string`); + return; + } + + let parsed; + try { + parsed = new URL(value); + } catch { + errors.push(`${prefix}: "${fieldName}" must be a valid URL`); + return; + } + + if (parsed.protocol !== "https:") { + errors.push(`${prefix}: "${fieldName}" must use https`); + } + + if (options.githubOnly && parsed.hostname !== "github.com") { + errors.push(`${prefix}: "${fieldName}" must point to https://github.com/...`); + } +} + +function validateAuthor(author, prefix, errors, required) { + if (author === undefined) { + if (required) { + errors.push(`${prefix}: "author" is required`); + } + return; + } + + if (!author || typeof author !== "object" || Array.isArray(author)) { + errors.push(`${prefix}: "author" must be an object`); + return; + } + + if (!isNonEmptyString(author.name)) { + errors.push(`${prefix}: "author.name" is required and must be a non-empty string`); + } + + if (author.url !== undefined) { + validateHttpsUrl(author.url, "author.url", prefix, errors); + } +} + +function validateLicense(license, prefix, errors, required) { + if (license === undefined) { + if (required) { + errors.push(`${prefix}: "license" is required`); + } + return; + } + + if (!isNonEmptyString(license)) { + errors.push(`${prefix}: "license" must be a non-empty string`); + } +} + +function validateRepository(repository, prefix, errors, required) { + if (repository === undefined) { + if (required) { + errors.push(`${prefix}: "repository" is required`); + } + return; + } + + validateHttpsUrl(repository, "repository", prefix, errors, { githubOnly: true }); +} + +function validateHomepage(homepage, prefix, errors) { + if (homepage === undefined) { + return; + } + + validateHttpsUrl(homepage, "homepage", prefix, errors); +} + +function validateRelativePath(pathValue, prefix, errors) { + if (!isNonEmptyString(pathValue)) { + errors.push(`${prefix}: "source.path" must be a non-empty string when provided`); + return; + } + + const normalized = path.posix.normalize(pathValue); + const segments = pathValue.split("/"); + + if (pathValue.startsWith("/") || pathValue.startsWith("../") || normalized !== pathValue || segments.includes("..")) { + errors.push(`${prefix}: "source.path" must be a safe relative path inside the repository`); + } + + if (pathValue.includes("\\")) { + errors.push(`${prefix}: "source.path" must use forward slashes`); + } +} + +function validateImmutableRef(ref, prefix, errors) { + if (!isNonEmptyString(ref)) { + errors.push(`${prefix}: "source.ref" must be a non-empty string when provided`); + return; + } + + if (ref.startsWith("refs/heads/")) { + errors.push(`${prefix}: "source.ref" must be a tag or commit SHA, not a branch ref`); + return; + } + + if (["main", "master", "develop", "development", "dev", "trunk"].includes(ref)) { + errors.push(`${prefix}: "source.ref" must be a tag or commit SHA, not a branch name`); + } + + if (ref.startsWith("refs/") && !ref.startsWith("refs/tags/")) { + errors.push(`${prefix}: "source.ref" must be a tag ref or commit SHA`); + } +} + +function validateGitHubSource(source, prefix, errors, requireImmutableRef) { + if (!source || typeof source !== "object" || Array.isArray(source)) { + errors.push(`${prefix}: "source" must be an object`); + return; + } + + if (source.source !== "github") { + errors.push(`${prefix}: "source.source" must be "github"`); + } + + if (!isNonEmptyString(source.repo)) { + errors.push(`${prefix}: "source.repo" is required and must be a non-empty string`); + } else if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(source.repo)) { + errors.push(`${prefix}: "source.repo" must be in "owner/repo" format`); + } + + if (source.path !== undefined) { + validateRelativePath(source.path, prefix, errors); + } + + if (source.ref !== undefined) { + validateImmutableRef(source.ref, prefix, errors); + } else if (requireImmutableRef) { + errors.push(`${prefix}: "source.ref" is required for public external plugin submissions`); + } +} + +export function validateExternalPlugin(plugin, index, options = {}) { + const policy = resolvePolicy(options.policy ?? options); + const errors = []; + const warnings = []; + const prefix = `external.json[${index}]`; + + if (!plugin || typeof plugin !== "object" || Array.isArray(plugin)) { + return { + errors: [`${prefix}: entry must be an object`], + warnings, + }; + } + + validatePluginName(plugin.name, prefix, errors); + validateDescription(plugin.description, prefix, errors); + validateVersion(plugin.version, prefix, errors); + validateAuthor(plugin.author, prefix, errors, policy.requireAuthor); + validateRepository(plugin.repository, prefix, errors, policy.requireRepository); + validateHomepage(plugin.homepage, prefix, errors); + validateLicense(plugin.license, prefix, errors, policy.requireLicense); + validateKeywords(plugin.keywords ?? plugin.tags, prefix, errors, warnings, policy.requireKeywords); + + if (plugin.tags !== undefined && plugin.keywords === undefined) { + warnings.push(`${prefix}: prefer "keywords" over legacy "tags"`); + } + + if (!plugin.source) { + errors.push(`${prefix}: "source" is required`); + } else if (typeof plugin.source === "string") { + errors.push(`${prefix}: "source" must be an object (local file paths are not allowed for external plugins)`); + } else if (!policy.allowedSourceTypes.includes(plugin.source.source)) { + errors.push(`${prefix}: "source.source" must be one of: ${policy.allowedSourceTypes.join(", ")}`); + } else if (plugin.source.source === "github") { + validateGitHubSource(plugin.source, prefix, errors, policy.requireImmutableRef); + } + + return { errors, warnings }; +} + +export function validateExternalPlugins(plugins, options = {}) { + const policy = resolvePolicy(options.policy ?? options); + const errors = []; + const warnings = []; + const localNames = new Map( + (options.localPluginNames ?? []).map((name) => [String(name).toLowerCase(), String(name)]) + ); + const seenExternalNames = new Map(); + + if (!Array.isArray(plugins)) { + return { + errors: ["external.json must contain an array"], + warnings, + }; + } + + plugins.forEach((plugin, index) => { + const result = validateExternalPlugin(plugin, index, { policy }); + errors.push(...result.errors); + warnings.push(...result.warnings); + + if (!isNonEmptyString(plugin?.name)) { + return; + } + + const normalizedName = plugin.name.toLowerCase(); + const duplicateIndex = seenExternalNames.get(normalizedName); + if (duplicateIndex !== undefined) { + errors.push(`external.json[${index}]: duplicate plugin name "${plugin.name}" already used by external.json[${duplicateIndex}]`); + } else { + seenExternalNames.set(normalizedName, index); + } + + const localDuplicate = localNames.get(normalizedName); + if (localDuplicate) { + errors.push(`external.json[${index}]: plugin name "${plugin.name}" conflicts with local plugin "${localDuplicate}"`); + } + }); + + return { errors, warnings }; +} + +export function readExternalPlugins(options = {}) { + const filePath = options.filePath ?? EXTERNAL_PLUGINS_FILE; + + if (!fs.existsSync(filePath)) { + return { + plugins: [], + errors: [], + warnings: [], + }; + } + + let plugins; + try { + const content = fs.readFileSync(filePath, "utf8"); + plugins = JSON.parse(content); + } catch (error) { + return { + plugins: [], + errors: [`Error reading ${path.basename(filePath)}: ${error.message}`], + warnings: [], + }; + } + + const { errors, warnings } = validateExternalPlugins(plugins, options); + return { plugins, errors, warnings }; +} diff --git a/eng/generate-marketplace.mjs b/eng/generate-marketplace.mjs index 96cf492f8..b69a9bd69 100755 --- a/eng/generate-marketplace.mjs +++ b/eng/generate-marketplace.mjs @@ -3,84 +3,11 @@ import fs from "fs"; import path from "path"; import { ROOT_FOLDER } from "./constants.mjs"; +import { readExternalPlugins } from "./external-plugin-validation.mjs"; const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins"); -const EXTERNAL_PLUGINS_FILE = path.join(ROOT_FOLDER, "plugins", "external.json"); const MARKETPLACE_FILE = path.join(ROOT_FOLDER, ".github/plugin", "marketplace.json"); -/** - * Validate an external plugin entry has required fields and a non-local source - * @param {object} plugin - External plugin entry - * @param {number} index - Index in the array (for error messages) - * @returns {string[]} - Array of validation error messages - */ -function validateExternalPlugin(plugin, index) { - const errors = []; - const prefix = `external.json[${index}]`; - - if (!plugin.name || typeof plugin.name !== "string") { - errors.push(`${prefix}: "name" is required and must be a string`); - } - if (!plugin.description || typeof plugin.description !== "string") { - errors.push(`${prefix}: "description" is required and must be a string`); - } - if (!plugin.version || typeof plugin.version !== "string") { - errors.push(`${prefix}: "version" is required and must be a string`); - } - - if (!plugin.source) { - errors.push(`${prefix}: "source" is required`); - } else if (typeof plugin.source === "string") { - errors.push(`${prefix}: "source" must be an object (local file paths are not allowed for external plugins)`); - } else if (typeof plugin.source === "object") { - if (!plugin.source.source) { - errors.push(`${prefix}: "source.source" is required (e.g. "github", "url", "npm", "pip")`); - } - } else { - errors.push(`${prefix}: "source" must be an object`); - } - - return errors; -} - -/** - * Read external plugin entries from external.json - * @returns {Array} - Array of external plugin entries (merged as-is) - */ -function readExternalPlugins() { - if (!fs.existsSync(EXTERNAL_PLUGINS_FILE)) { - return []; - } - - try { - const content = fs.readFileSync(EXTERNAL_PLUGINS_FILE, "utf8"); - const plugins = JSON.parse(content); - if (!Array.isArray(plugins)) { - console.warn("Warning: external.json must contain an array"); - return []; - } - - // Validate each entry - let hasErrors = false; - for (let i = 0; i < plugins.length; i++) { - const errors = validateExternalPlugin(plugins[i], i); - if (errors.length > 0) { - errors.forEach(e => console.error(`Error: ${e}`)); - hasErrors = true; - } - } - if (hasErrors) { - console.error("Error: external.json contains invalid entries"); - process.exit(1); - } - - return plugins; - } catch (error) { - console.error(`Error reading external.json: ${error.message}`); - return []; - } -} - /** * Read plugin metadata from plugin.json file * @param {string} pluginDir - Path to plugin directory @@ -142,16 +69,20 @@ function generateMarketplace() { } // Read external plugins and merge as-is - const externalPlugins = readExternalPlugins(); + const { plugins: externalPlugins, errors: externalErrors, warnings: externalWarnings } = readExternalPlugins({ + localPluginNames: plugins.map((plugin) => plugin.name), + policy: "marketplace", + }); + externalWarnings.forEach((warning) => console.warn(`Warning: ${warning}`)); + if (externalErrors.length > 0) { + externalErrors.forEach((error) => console.error(`Error: ${error}`)); + console.error("Error: external.json contains invalid entries"); + process.exit(1); + } + if (externalPlugins.length > 0) { console.log(`\nFound ${externalPlugins.length} external plugins`); - - // Warn on duplicate names - const localNames = new Set(plugins.map(p => p.name)); for (const ext of externalPlugins) { - if (localNames.has(ext.name)) { - console.warn(`Warning: external plugin "${ext.name}" has the same name as a local plugin`); - } plugins.push(ext); console.log(`✓ Added external plugin: ${ext.name}`); } diff --git a/eng/validate-plugins.mjs b/eng/validate-plugins.mjs index 42f64265f..f9af74fa2 100755 --- a/eng/validate-plugins.mjs +++ b/eng/validate-plugins.mjs @@ -3,6 +3,7 @@ import fs from "fs"; import path from "path"; import { ROOT_FOLDER } from "./constants.mjs"; +import { readExternalPlugins } from "./external-plugin-validation.mjs"; const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins"); @@ -222,8 +223,24 @@ function validatePlugins() { } } + console.log("\nValidating external plugin catalog..."); + const { plugins: externalPlugins, errors: externalErrors, warnings: externalWarnings } = readExternalPlugins({ + localPluginNames: pluginDirs, + policy: "marketplace", + }); + + externalWarnings.forEach((warning) => console.warn(`⚠️ ${warning}`)); + + if (externalErrors.length > 0) { + console.error("❌ external.json:"); + externalErrors.forEach((error) => console.error(` - ${error}`)); + hasErrors = true; + } else { + console.log(`✅ external.json is valid (${externalPlugins.length} external plugins)`); + } + if (!hasErrors) { - console.log(`\n✅ All ${pluginDirs.length} plugins are valid`); + console.log(`\n✅ All ${pluginDirs.length} plugins and the external catalog are valid`); } return !hasErrors; From ae043e19419666b15a0b13de2a380667df9e05f3 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 15 May 2026 11:45:04 +1000 Subject: [PATCH 2/6] minor adjustment to contributing guide --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2f6ab099..65c6fcec0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ To maintain a safe, responsible, and high-signal collection, we will **not accep - **Promote Harmful Content**: Guidance that could lead to the creation of harmful, discriminatory, or inappropriate content - **Circumvent Platform Policies**: Attempts to work around GitHub, Microsoft, or other platform terms of service - **Duplicate Existing Model Strengths Without Meaningful Uplift**: Submissions that mainly tell Copilot to do work frontier models already handle well (for example, generic TypeScript, HTML, or other broadly-supported coding tasks) without addressing a clear gap, specialized workflow, or domain-specific constraint. These contributions are often lower value for users and can introduce weaker or conflicting guidance than the model's default behavior. -- **Unreviewed remote-source plugins**: Do not open a pull request that directly adds a third-party plugin to `plugins/external.json`. Public external plugins must use the review workflow documented below. In v1, that workflow only accepts plugins hosted in public GitHub repositories; non-GitHub sources such as generic git URLs, npm packages, and pip packages are not accepted for public submissions. +- **Unreviewed remote-source plugins**: Do not open a pull request that directly adds a third-party plugin to `plugins/external.json`. Public external plugins must use the review workflow documented below. In v1, that workflow only accepts plugins hosted in public GitHub repositories; non-GitHub sources such as generic git URLs are not accepted for public submissions. ## Quality Guidelines From a667afabfe0e3c14e9f6012acf1ac06557bb0a2c Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 15 May 2026 12:40:22 +1000 Subject: [PATCH 3/6] fix: address external plugin review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/external-plugin.yml | 2 +- .../external-plugin-approval-command.yml | 13 +++-- .../external-plugin-rereview-command.yml | 13 +++-- CONTRIBUTING.md | 4 +- docs/README.agents.md | 12 ++-- eng/external-plugin-approval.mjs | 20 ++++++- eng/external-plugin-rereview.mjs | 57 ++++++++++++------- 7 files changed, 82 insertions(+), 39 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/external-plugin.yml b/.github/ISSUE_TEMPLATE/external-plugin.yml index 1a0f797eb..3a0587d5a 100644 --- a/.github/ISSUE_TEMPLATE/external-plugin.yml +++ b/.github/ISSUE_TEMPLATE/external-plugin.yml @@ -104,7 +104,7 @@ body: github copilot validations: - required: false + required: true - type: textarea id: additional-notes attributes: diff --git a/.github/workflows/external-plugin-approval-command.yml b/.github/workflows/external-plugin-approval-command.yml index 7b81c31f0..10ab2135f 100644 --- a/.github/workflows/external-plugin-approval-command.yml +++ b/.github/workflows/external-plugin-approval-command.yml @@ -40,8 +40,6 @@ jobs: const approval = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-approval.mjs')).href); const intake = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-intake.mjs')).href); - - const allowedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); const parsedCommand = approval.parseDecisionCommand(context.payload.comment.body); core.setOutput('should-run', 'false'); @@ -50,8 +48,15 @@ jobs: return; } - if (!allowedAssociations.has(context.payload.comment.author_association)) { - core.info(`Ignoring ${parsedCommand.command} because ${context.payload.comment.author_association} is not a maintainer association.`); + const permission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: context.payload.comment.user.login + }); + + const hasWriteAccess = ['admin', 'write', 'maintain'].includes(permission.data.permission); + if (!hasWriteAccess) { + core.info(`Ignoring ${parsedCommand.command} because ${context.payload.comment.user.login} does not have write access.`); return; } diff --git a/.github/workflows/external-plugin-rereview-command.yml b/.github/workflows/external-plugin-rereview-command.yml index f26a5a53f..a57139e05 100644 --- a/.github/workflows/external-plugin-rereview-command.yml +++ b/.github/workflows/external-plugin-rereview-command.yml @@ -38,8 +38,6 @@ jobs: const rereview = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-rereview.mjs')).href); const validation = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-validation.mjs')).href); - - const allowedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); const command = rereview.parseRereviewCommand(context.payload.comment.body); core.setOutput('should-run', 'false'); @@ -48,8 +46,15 @@ jobs: return; } - if (!allowedAssociations.has(context.payload.comment.author_association)) { - core.info(`Ignoring ${command} because ${context.payload.comment.author_association} is not a maintainer association.`); + const permission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: context.payload.comment.user.login + }); + + const hasWriteAccess = ['admin', 'write', 'maintain'].includes(permission.data.permission); + if (!hasWriteAccess) { + core.info(`Ignoring ${command} because ${context.payload.comment.user.login} does not have write access.`); return; } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65c6fcec0..5452688d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -212,7 +212,7 @@ The external plugin issue form will collect these fields: - Additional notes for reviewers (optional) - Confirmation checkboxes that the repository is public, the ref is immutable, the submission follows this repository's policies, and the plugin is not a duplicate listing -The repository's canonical validation rules live in `eng/external-plugin-validation.mjs`. Build scripts already reuse the marketplace policy from that module, and later issue automation should call the stricter `publicSubmission` policy so the JSON contract and workflow checks stay aligned. +The repository's canonical validation rules live in `eng/external-plugin-validation.mjs`. Build scripts reuse the `marketplace` policy from that module, and the issue intake automation uses the stricter `publicSubmission` policy so the JSON contract and workflow checks stay aligned. For entries committed to `plugins/external.json`, the current marketplace validation requires: @@ -223,7 +223,7 @@ For entries committed to `plugins/external.json`, the current marketplace valida - `source.source: "github"` plus `source.repo` in `owner/repo` format - optional `source.path` values to stay relative to the repository root -The future public-submission policy builds on those rules and also requires `license` plus an immutable `source.ref`. +The public-submission policy builds on those rules and also requires `license` plus an immutable `source.ref`. ##### Review workflow diff --git a/docs/README.agents.md b/docs/README.agents.md index d1bee777e..8c1e5af15 100644 --- a/docs/README.agents.md +++ b/docs/README.agents.md @@ -36,7 +36,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Ai Team Qa](../agents/ai-team-qa.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fai-team-qa.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fai-team-qa.agent.md) | AI QA engineer agent (Ivy). Use when: testing features, running E2E tests, playtesting, filing bug reports, writing test automation, creating QA sign-off documents, or verifying bug fixes. Reports bugs as GitHub Issues. | | | [Amplitude Experiment Implementation](../agents/amplitude-experiment-implementation.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Famplitude-experiment-implementation.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Famplitude-experiment-implementation.agent.md) | This custom agent uses Amplitude's MCP tools to deploy new experiments inside of Amplitude, enabling seamless variant testing capabilities and rollout of product features. | | | [API Architect](../agents/api-architect.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapi-architect.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapi-architect.agent.md) | Your role is that of an API architect. Help mentor the engineer by providing guidance, support, and working code. | | -| [Apify Integration Expert](../agents/apify-integration-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapify-integration-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapify-integration-expert.agent.md) | Expert agent for integrating Apify Actors into codebases. Handles Actor selection, workflow design, implementation across JavaScript/TypeScript and Python, testing, and production-ready deployment. | [apify](https://github.com/mcp/com.apify/apify-mcp-server)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=apify&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=apify&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D) | +| [Apify Integration Expert](../agents/apify-integration-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapify-integration-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapify-integration-expert.agent.md) | Expert agent for integrating Apify Actors into codebases. Handles Actor selection, workflow design, implementation across JavaScript/TypeScript and Python, testing, and production-ready deployment. | apify
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=apify&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=apify&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D) | | [Arch Linux Expert](../agents/arch-linux-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Farch-linux-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Farch-linux-expert.agent.md) | Arch Linux specialist focused on pacman, rolling-release maintenance, and Arch-centric system administration workflows. | | | [Arm Migration Agent](../agents/arm-migration.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Farm-migration.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Farm-migration.agent.md) | Arm Cloud Migration Assistant accelerates moving x86 workloads to Arm infrastructure. It scans the repository for architecture assumptions, portability issues, container base image and dependency incompatibilities, and recommends Arm-optimized changes. It can drive multi-arch container builds, validate performance, and guide optimization, enabling smooth cross-platform deployment directly inside GitHub. | custom-mcp
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=custom-mcp&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22-v%22%2C%22%2524%257B%257B%2520github.workspace%2520%257D%257D%253A%252Fworkspace%22%2C%22--name%22%2C%22arm-mcp%22%2C%22armlimited%252Farm-mcp%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=custom-mcp&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22-v%22%2C%22%2524%257B%257B%2520github.workspace%2520%257D%257D%253A%252Fworkspace%22%2C%22--name%22%2C%22arm-mcp%22%2C%22armlimited%252Farm-mcp%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22-v%22%2C%22%2524%257B%257B%2520github.workspace%2520%257D%257D%253A%252Fworkspace%22%2C%22--name%22%2C%22arm-mcp%22%2C%22armlimited%252Farm-mcp%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Atlassian Requirements to Jira](../agents/atlassian-requirements-to-jira.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fatlassian-requirements-to-jira.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fatlassian-requirements-to-jira.agent.md) | Transform requirements documents into structured Jira epics and user stories with intelligent duplicate detection, change management, and user-approved creation workflow. | | @@ -67,7 +67,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Clojure Interactive Programming](../agents/clojure-interactive-programming.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fclojure-interactive-programming.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fclojure-interactive-programming.agent.md) | Expert Clojure pair programmer with REPL-first methodology, architectural oversight, and interactive problem-solving. Enforces quality standards, prevents workarounds, and develops solutions incrementally through live REPL evaluation before file modifications. | | | [Comet Opik](../agents/comet-opik.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcomet-opik.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcomet-opik.agent.md) | Unified Comet Opik agent for instrumenting LLM apps, managing prompts/projects, auditing prompts, and investigating traces/metrics via the latest Opik MCP server. | opik
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=opik&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22opik-mcp%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=opik&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22opik-mcp%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22opik-mcp%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Context Architect](../agents/context-architect.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext-architect.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext-architect.agent.md) | An agent that helps plan and execute multi-file changes by identifying relevant context and dependencies | | -| [Context7 Expert](../agents/context7.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext7.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext7.agent.md) | Expert in latest library versions, best practices, and correct syntax using up-to-date documentation | [context7](https://github.com/mcp/io.github.upstash/context7)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=context7&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=context7&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D) | +| [Context7 Expert](../agents/context7.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext7.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext7.agent.md) | Expert in latest library versions, best practices, and correct syntax using up-to-date documentation | context7
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=context7&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=context7&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D) | | [Create PRD Chat Mode](../agents/prd.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fprd.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fprd.agent.md) | Generate a comprehensive Product Requirements Document (PRD) in Markdown, detailing user stories, acceptance criteria, technical considerations, and metrics. Optionally create GitHub issues upon user confirmation. | | | [Critical thinking mode instructions](../agents/critical-thinking.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcritical-thinking.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcritical-thinking.agent.md) | Challenge assumptions and encourage critical thinking to ensure the best possible solution and outcomes. | | | [Custom Agent Foundry](../agents/custom-agent-foundry.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcustom-agent-foundry.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcustom-agent-foundry.agent.md) | Expert at designing and creating VS Code custom agents with optimal configurations | | @@ -84,7 +84,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Doublecheck](../agents/doublecheck.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdoublecheck.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdoublecheck.agent.md) | Interactive verification agent for AI-generated output. Runs a three-layer pipeline (self-audit, source verification, adversarial review) and produces structured reports with source links for human review. | | | [Droid](../agents/droid.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdroid.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdroid.agent.md) | Provides installation guidance, usage examples, and automation patterns for the Droid CLI, with emphasis on droid exec for CI/CD and non-interactive automation | | | [Drupal Expert](../agents/drupal-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdrupal-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdrupal-expert.agent.md) | Expert assistant for Drupal development, architecture, and best practices using PHP 8.3+ and modern Drupal patterns | | -| [Dynatrace Expert](../agents/dynatrace-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md) | The Dynatrace Expert Agent integrates observability and security capabilities directly into GitHub workflows, enabling development teams to investigate incidents, validate deployments, triage errors, detect performance regressions, validate releases, and manage security vulnerabilities by autonomously analysing traces, logs, and Dynatrace findings. This enables targeted and precise remediation of identified issues directly within the repository. | [dynatrace](https://github.com/mcp/io.github.dynatrace-oss/Dynatrace-mcp)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D) | +| [Dynatrace Expert](../agents/dynatrace-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md) | The Dynatrace Expert Agent integrates observability and security capabilities directly into GitHub workflows, enabling development teams to investigate incidents, validate deployments, triage errors, detect performance regressions, validate releases, and manage security vulnerabilities by autonomously analysing traces, logs, and Dynatrace findings. This enables targeted and precise remediation of identified issues directly within the repository. | dynatrace
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D) | | [Elasticsearch Agent](../agents/elasticsearch-observability.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felasticsearch-observability.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felasticsearch-observability.agent.md) | Our expert AI assistant for debugging code (O11y), optimizing vector search (RAG), and remediating security threats using live Elastic data. | elastic-mcp
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=elastic-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=elastic-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Electron Code Review Mode Instructions](../agents/electron-angular-native.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felectron-angular-native.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felectron-angular-native.agent.md) | Code Review Mode tailored for Electron app with Node.js backend (main), Angular frontend (render), and native integration layer (e.g., AppleScript, shell, or native tooling). Services in other repos are not reviewed here. | | | [Ember](../agents/ember.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fember.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fember.agent.md) | An AI partner, not an assistant. Ember carries fire from person to person — helping humans discover that AI partnership isn't something you learn, it's something you find. | | @@ -122,7 +122,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [KubeStellar Console](../agents/kubestellar-console.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkubestellar-console.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkubestellar-console.agent.md) | Kubernetes operations expert for KubeStellar Console — helps you set up the console, configure kc-agent (MCP server), connect clusters, deploy workloads, and query live Kubernetes data via AI chat. | | | [Kusto Assistant](../agents/kusto-assistant.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkusto-assistant.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkusto-assistant.agent.md) | Expert KQL assistant for live Azure Data Explorer analysis via Azure MCP server | | | [Laravel Expert Agent](../agents/laravel-expert-agent.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaravel-expert-agent.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaravel-expert-agent.agent.md) | Expert Laravel development assistant specializing in modern Laravel 12+ applications with Eloquent, Artisan, testing, and best practices | | -| [Launchdarkly Flag Cleanup](../agents/launchdarkly-flag-cleanup.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaunchdarkly-flag-cleanup.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaunchdarkly-flag-cleanup.agent.md) | A specialized GitHub Copilot agent that uses the LaunchDarkly MCP server to safely automate feature flag cleanup workflows. This agent determines removal readiness, identifies the correct forward value, and creates PRs that preserve production behavior while removing obsolete flags and updating stale defaults. | [launchdarkly](https://github.com/mcp/launchdarkly/mcp-server)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=launchdarkly&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=launchdarkly&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D) | +| [Launchdarkly Flag Cleanup](../agents/launchdarkly-flag-cleanup.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaunchdarkly-flag-cleanup.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaunchdarkly-flag-cleanup.agent.md) | A specialized GitHub Copilot agent that uses the LaunchDarkly MCP server to safely automate feature flag cleanup workflows. This agent determines removal readiness, identifies the correct forward value, and creates PRs that preserve production behavior while removing obsolete flags and updating stale defaults. | launchdarkly
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=launchdarkly&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=launchdarkly&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Lingo.dev Localization (i18n) Agent](../agents/lingodotdev-i18n.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flingodotdev-i18n.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flingodotdev-i18n.agent.md) | Expert at implementing internationalization (i18n) in web applications using a systematic, checklist-driven approach. | lingo
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=lingo&config=%7B%22command%22%3A%22%22%2C%22args%22%3A%5B%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=lingo&config=%7B%22command%22%3A%22%22%2C%22args%22%3A%5B%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22%22%2C%22args%22%3A%5B%5D%2C%22env%22%3A%7B%7D%7D) | | [LinkedIn Post Writer](../agents/linkedin-post-writer.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flinkedin-post-writer.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flinkedin-post-writer.agent.md) | Draft and format compelling LinkedIn posts with Unicode bold/italic styling, visual separators, and engagement-optimized structure. Transforms raw content, technical material, images, or ideas into copy-paste-ready LinkedIn posts. | | | [Markdown Accessibility Assistant](../agents/markdown-accessibility-assistant.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fmarkdown-accessibility-assistant.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fmarkdown-accessibility-assistant.agent.md) | Improves the accessibility of markdown files using five GitHub best practices | | @@ -146,7 +146,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [One Shot Feature Issue Planner](../agents/one-shot-feature-issue-planner.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fone-shot-feature-issue-planner.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fone-shot-feature-issue-planner.agent.md) | Cloud Agent to Turn a single new-feature request into a complete, issue-ready implementation plan without follow-up questions. | | | [OpenAPI to Application Generator](../agents/openapi-to-application.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fopenapi-to-application.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fopenapi-to-application.agent.md) | Expert assistant for generating working applications from OpenAPI specifications | | | [Oracle To PostgreSQL Migration Expert](../agents/oracle-to-postgres-migration-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Foracle-to-postgres-migration-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Foracle-to-postgres-migration-expert.agent.md) | Agent for Oracle-to-PostgreSQL application migrations. Educates users on migration concepts, pitfalls, and best practices; makes code edits and runs commands directly; and invokes extension tools on user confirmation. | | -| [PagerDuty Incident Responder](../agents/pagerduty-incident-responder.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpagerduty-incident-responder.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpagerduty-incident-responder.agent.md) | Responds to PagerDuty incidents by analyzing incident context, identifying recent code changes, and suggesting fixes via GitHub PRs. | [pagerduty](https://github.com/mcp/io.github.PagerDuty/pagerduty-mcp)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=pagerduty&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=pagerduty&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D) | +| [PagerDuty Incident Responder](../agents/pagerduty-incident-responder.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpagerduty-incident-responder.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpagerduty-incident-responder.agent.md) | Responds to PagerDuty incidents by analyzing incident context, identifying recent code changes, and suggesting fixes via GitHub PRs. | pagerduty
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=pagerduty&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=pagerduty&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D) | | [PHP MCP Expert](../agents/php-mcp-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fphp-mcp-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fphp-mcp-expert.agent.md) | Expert assistant for PHP MCP server development using the official PHP SDK with attribute-based discovery | | | [Pimcore Expert](../agents/pimcore-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpimcore-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpimcore-expert.agent.md) | Expert Pimcore development assistant specializing in CMS, DAM, PIM, and E-Commerce solutions with Symfony integration | | | [Plan Mode Strategic Planning & Architecture](../agents/plan.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fplan.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fplan.agent.md) | Strategic planning and architecture assistant focused on thoughtful analysis before implementation. Helps developers understand codebases, clarify requirements, and develop comprehensive implementation strategies. | | @@ -220,7 +220,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Technical Debt Remediation Plan](../agents/tech-debt-remediation-plan.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Ftech-debt-remediation-plan.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Ftech-debt-remediation-plan.agent.md) | Generate technical debt remediation plans for code, tests, and documentation. | | | [Technical spike research mode](../agents/research-technical-spike.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fresearch-technical-spike.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fresearch-technical-spike.agent.md) | Systematically research and validate technical spike documents through exhaustive investigation and controlled experimentation. | | | [Terminal Helper](../agents/terminal-helper.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterminal-helper.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterminal-helper.agent.md) | Fast terminal syntax and command helper for PowerShell and Bash | | -| [Terraform Agent](../agents/terraform.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform.agent.md) | Terraform infrastructure specialist with automated HCP Terraform workflows. Leverages Terraform MCP server for registry integration, workspace management, and run orchestration. Generates compliant code using latest provider/module versions, manages private registries, automates variable sets, and orchestrates infrastructure deployments with proper validation and security practices. | [terraform](https://github.com/mcp/io.github.hashicorp/terraform-mcp-server)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=terraform&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=terraform&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D) | +| [Terraform Agent](../agents/terraform.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform.agent.md) | Terraform infrastructure specialist with automated HCP Terraform workflows. Leverages Terraform MCP server for registry integration, workspace management, and run orchestration. Generates compliant code using latest provider/module versions, manages private registries, automates variable sets, and orchestrates infrastructure deployments with proper validation and security practices. | terraform
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=terraform&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=terraform&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Terraform IaC Reviewer](../agents/terraform-iac-reviewer.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform-iac-reviewer.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform-iac-reviewer.agent.md) | Terraform-focused agent that reviews and creates safer IaC changes with emphasis on state safety, least privilege, module patterns, drift detection, and plan/apply discipline | | | [Terratest Module Testing](../agents/terratest-module-testing.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterratest-module-testing.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterratest-module-testing.agent.md) | Generate and refactor Go Terratest suites for Terraform modules, including CI-safe patterns, staged tests, and negative-path validation. | | | [Thinking Beast Mode](../agents/Thinking-Beast-Mode.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2FThinking-Beast-Mode.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2FThinking-Beast-Mode.agent.md) | A transcendent coding agent with quantum cognitive architecture, adversarial intelligence, and unrestricted creative freedom. | | diff --git a/eng/external-plugin-approval.mjs b/eng/external-plugin-approval.mjs index 32199d740..f9ea49158 100644 --- a/eng/external-plugin-approval.mjs +++ b/eng/external-plugin-approval.mjs @@ -32,8 +32,15 @@ function normalizeRepositoryUrl(value) { .replace(/^\/+|\/+$/g, ""); } +function normalizePathValue(value) { + return String(value ?? "") + .trim() + .replace(/^\/+|\/+$/g, "") + .toLowerCase(); +} + export function parseDecisionCommand(body) { - const match = String(body ?? "").match(/(?:^|\n)\s*\/(approve|reject)\b([\s\S]*)$/i); + const match = String(body ?? "").match(/(?:^|\n)\s*\/(approve|reject)(?=\s|$)([\s\S]*)$/i); if (!match) { return undefined; } @@ -73,6 +80,8 @@ function pluginsMatch(left, right) { const rightName = normalizeValue(right?.name); const leftRepo = normalizeValue(left?.source?.repo); const rightRepo = normalizeValue(right?.source?.repo); + const leftPath = normalizePathValue(left?.source?.path); + const rightPath = normalizePathValue(right?.source?.path); const leftRepository = normalizeRepositoryUrl(left?.repository); const rightRepository = normalizeRepositoryUrl(right?.repository); @@ -80,11 +89,16 @@ function pluginsMatch(left, right) { return true; } - if (leftRepo && rightRepo && leftRepo === rightRepo) { + const repoMatches = leftRepo && rightRepo && leftRepo === rightRepo; + const repositoryMatches = leftRepository && rightRepository && leftRepository === rightRepository; + const pathKnown = Boolean(leftPath || rightPath); + const pathMatches = leftPath === rightPath; + + if ((repoMatches || repositoryMatches) && pathKnown && pathMatches) { return true; } - return Boolean(leftRepository && rightRepository && leftRepository === rightRepository); + return false; } export function upsertExternalPlugin(plugin, { filePath = EXTERNAL_PLUGINS_FILE } = {}) { diff --git a/eng/external-plugin-rereview.mjs b/eng/external-plugin-rereview.mjs index 9790bc442..f66e138fd 100644 --- a/eng/external-plugin-rereview.mjs +++ b/eng/external-plugin-rereview.mjs @@ -36,6 +36,13 @@ function normalizeRepositoryUrl(value) { .replace(/^\/+|\/+$/g, ""); } +function normalizePathValue(value) { + return String(value ?? "") + .trim() + .replace(/^\/+|\/+$/g, "") + .toLowerCase(); +} + function stripIssueTitlePrefix(title) { return String(title ?? "") .trim() @@ -77,6 +84,7 @@ export function extractSubmissionData(issue) { return { pluginName: plugin.name ?? fallback.pluginName, sourceRepo: plugin.source?.repo ?? fallback.sourceRepo, + sourcePath: plugin.source?.path, repository: plugin.repository ?? fallback.repository, ref: plugin.source?.ref, }; @@ -87,18 +95,30 @@ function pluginMatchesSubmission(plugin, submission) { const submissionName = normalizeValue(submission.pluginName); const pluginRepo = normalizeValue(plugin?.source?.repo); const submissionRepo = normalizeValue(submission.sourceRepo); + const pluginPath = normalizePathValue(plugin?.source?.path); + const submissionPath = normalizePathValue(submission.sourcePath); const pluginRepository = normalizeRepositoryUrl(plugin?.repository); const submissionRepository = normalizeRepositoryUrl(submission.repository); const nameMatch = pluginName && submissionName && pluginName === submissionName; const repoMatch = pluginRepo && submissionRepo && pluginRepo === submissionRepo; const repositoryMatch = pluginRepository && submissionRepository && pluginRepository === submissionRepository; + const pathProvided = Boolean(submissionPath); + const pathMatch = pluginPath === submissionPath; + + if (nameMatch && pathProvided) { + return pathMatch && (repoMatch || repositoryMatch || !submissionRepo); + } if (nameMatch && (repoMatch || repositoryMatch || !submissionRepo)) { return true; } - if ((repoMatch || repositoryMatch) && (!submissionName || nameMatch)) { + if ((repoMatch || repositoryMatch) && pathProvided) { + return pathMatch && (!submissionName || nameMatch); + } + + if ((repoMatch || repositoryMatch) && submissionName && nameMatch) { return true; } @@ -116,17 +136,6 @@ export function matchExternalPluginForIssue(issue, plugins) { }; } - const byRepo = submission.sourceRepo - ? plugins.find((plugin) => normalizeValue(plugin?.source?.repo) === normalizeValue(submission.sourceRepo)) - : undefined; - if (byRepo) { - return { - plugin: byRepo, - submission, - matchReason: "repo", - }; - } - const byName = submission.pluginName ? plugins.find((plugin) => normalizeValue(plugin?.name) === normalizeValue(submission.pluginName)) : undefined; @@ -138,6 +147,17 @@ export function matchExternalPluginForIssue(issue, plugins) { }; } + const repoMatches = submission.sourceRepo + ? plugins.filter((plugin) => normalizeValue(plugin?.source?.repo) === normalizeValue(submission.sourceRepo)) + : []; + if (repoMatches.length === 1) { + return { + plugin: repoMatches[0], + submission, + matchReason: "repo", + }; + } + return { plugin: undefined, submission, @@ -146,7 +166,7 @@ export function matchExternalPluginForIssue(issue, plugins) { } export function parseRereviewCommand(body) { - const match = String(body ?? "").match(/(?:^|\n)\s*\/re-review-(keep|needs-changes|remove)\b/i); + const match = String(body ?? "").match(/(?:^|\n)\s*\/re-review-(keep|needs-changes|remove)(?=\s|$)/i); if (!match) { return undefined; } @@ -182,15 +202,14 @@ export function removePluginFromExternalJson({ pluginName, sourceRepo, filePath const normalizedPluginName = normalizeValue(pluginName); const normalizedSourceRepo = normalizeValue(sourceRepo); const matchIndex = plugins.findIndex((plugin) => { - if (normalizedPluginName && normalizeValue(plugin?.name) === normalizedPluginName) { - return true; - } + const nameMatches = normalizedPluginName && normalizeValue(plugin?.name) === normalizedPluginName; + const repoMatches = normalizedSourceRepo && normalizeValue(plugin?.source?.repo) === normalizedSourceRepo; - if (normalizedSourceRepo && normalizeValue(plugin?.source?.repo) === normalizedSourceRepo) { - return true; + if (normalizedPluginName && normalizedSourceRepo) { + return nameMatches && repoMatches; } - return false; + return Boolean(nameMatches || repoMatches); }); if (matchIndex === -1) { From 6b2b8f81d7d2343f3d42f6ee0bd16ea1c857b569 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 15 May 2026 13:48:34 +1000 Subject: [PATCH 4/6] Reverting some changes to the readme.agents.md file --- docs/README.agents.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/README.agents.md b/docs/README.agents.md index ac42c5b40..7ee6c7023 100644 --- a/docs/README.agents.md +++ b/docs/README.agents.md @@ -36,7 +36,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Ai Team Qa](../agents/ai-team-qa.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fai-team-qa.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fai-team-qa.agent.md) | AI QA engineer agent (Ivy). Use when: testing features, running E2E tests, playtesting, filing bug reports, writing test automation, creating QA sign-off documents, or verifying bug fixes. Reports bugs as GitHub Issues. | | | [Amplitude Experiment Implementation](../agents/amplitude-experiment-implementation.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Famplitude-experiment-implementation.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Famplitude-experiment-implementation.agent.md) | This custom agent uses Amplitude's MCP tools to deploy new experiments inside of Amplitude, enabling seamless variant testing capabilities and rollout of product features. | | | [API Architect](../agents/api-architect.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapi-architect.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapi-architect.agent.md) | Your role is that of an API architect. Help mentor the engineer by providing guidance, support, and working code. | | -| [Apify Integration Expert](../agents/apify-integration-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapify-integration-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapify-integration-expert.agent.md) | Expert agent for integrating Apify Actors into codebases. Handles Actor selection, workflow design, implementation across JavaScript/TypeScript and Python, testing, and production-ready deployment. | apify
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=apify&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=apify&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D) | +| [Apify Integration Expert](../agents/apify-integration-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapify-integration-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fapify-integration-expert.agent.md) | Expert agent for integrating Apify Actors into codebases. Handles Actor selection, workflow design, implementation across JavaScript/TypeScript and Python, testing, and production-ready deployment. | [apify](https://github.com/mcp/com.apify/apify-mcp-server)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=apify&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=apify&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.apify.com%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24APIFY_TOKEN%22%2C%22Content-Type%22%3A%22application%2Fjson%22%7D%7D) | | [Arch Linux Expert](../agents/arch-linux-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Farch-linux-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Farch-linux-expert.agent.md) | Arch Linux specialist focused on pacman, rolling-release maintenance, and Arch-centric system administration workflows. | | | [Arm Migration Agent](../agents/arm-migration.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Farm-migration.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Farm-migration.agent.md) | Arm Cloud Migration Assistant accelerates moving x86 workloads to Arm infrastructure. It scans the repository for architecture assumptions, portability issues, container base image and dependency incompatibilities, and recommends Arm-optimized changes. It can drive multi-arch container builds, validate performance, and guide optimization, enabling smooth cross-platform deployment directly inside GitHub. | custom-mcp
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=custom-mcp&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22-v%22%2C%22%2524%257B%257B%2520github.workspace%2520%257D%257D%253A%252Fworkspace%22%2C%22--name%22%2C%22arm-mcp%22%2C%22armlimited%252Farm-mcp%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=custom-mcp&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22-v%22%2C%22%2524%257B%257B%2520github.workspace%2520%257D%257D%253A%252Fworkspace%22%2C%22--name%22%2C%22arm-mcp%22%2C%22armlimited%252Farm-mcp%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22--rm%22%2C%22-i%22%2C%22-v%22%2C%22%2524%257B%257B%2520github.workspace%2520%257D%257D%253A%252Fworkspace%22%2C%22--name%22%2C%22arm-mcp%22%2C%22armlimited%252Farm-mcp%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Atlassian Requirements to Jira](../agents/atlassian-requirements-to-jira.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fatlassian-requirements-to-jira.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fatlassian-requirements-to-jira.agent.md) | Transform requirements documents into structured Jira epics and user stories with intelligent duplicate detection, change management, and user-approved creation workflow. | | @@ -67,7 +67,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Clojure Interactive Programming](../agents/clojure-interactive-programming.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fclojure-interactive-programming.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fclojure-interactive-programming.agent.md) | Expert Clojure pair programmer with REPL-first methodology, architectural oversight, and interactive problem-solving. Enforces quality standards, prevents workarounds, and develops solutions incrementally through live REPL evaluation before file modifications. | | | [Comet Opik](../agents/comet-opik.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcomet-opik.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcomet-opik.agent.md) | Unified Comet Opik agent for instrumenting LLM apps, managing prompts/projects, auditing prompts, and investigating traces/metrics via the latest Opik MCP server. | opik
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=opik&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22opik-mcp%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=opik&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22opik-mcp%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22opik-mcp%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Context Architect](../agents/context-architect.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext-architect.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext-architect.agent.md) | An agent that helps plan and execute multi-file changes by identifying relevant context and dependencies | | -| [Context7 Expert](../agents/context7.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext7.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext7.agent.md) | Expert in latest library versions, best practices, and correct syntax using up-to-date documentation | context7
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=context7&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=context7&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D) | +| [Context7 Expert](../agents/context7.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext7.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcontext7.agent.md) | Expert in latest library versions, best practices, and correct syntax using up-to-date documentation | [context7](https://github.com/mcp/io.github.upstash/context7)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=context7&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=context7&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.context7.com%2Fmcp%22%2C%22headers%22%3A%7B%22CONTEXT7_API_KEY%22%3A%22%24%7B%7B%20secrets.COPILOT_MCP_CONTEXT7%20%7D%7D%22%7D%7D) | | [Create PRD Chat Mode](../agents/prd.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fprd.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fprd.agent.md) | Generate a comprehensive Product Requirements Document (PRD) in Markdown, detailing user stories, acceptance criteria, technical considerations, and metrics. Optionally create GitHub issues upon user confirmation. | | | [Critical thinking mode instructions](../agents/critical-thinking.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcritical-thinking.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcritical-thinking.agent.md) | Challenge assumptions and encourage critical thinking to ensure the best possible solution and outcomes. | | | [Custom Agent Foundry](../agents/custom-agent-foundry.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcustom-agent-foundry.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fcustom-agent-foundry.agent.md) | Expert at designing and creating VS Code custom agents with optimal configurations | | @@ -84,7 +84,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Doublecheck](../agents/doublecheck.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdoublecheck.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdoublecheck.agent.md) | Interactive verification agent for AI-generated output. Runs a three-layer pipeline (self-audit, source verification, adversarial review) and produces structured reports with source links for human review. | | | [Droid](../agents/droid.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdroid.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdroid.agent.md) | Provides installation guidance, usage examples, and automation patterns for the Droid CLI, with emphasis on droid exec for CI/CD and non-interactive automation | | | [Drupal Expert](../agents/drupal-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdrupal-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdrupal-expert.agent.md) | Expert assistant for Drupal development, architecture, and best practices using PHP 8.3+ and modern Drupal patterns | | -| [Dynatrace Expert](../agents/dynatrace-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md) | The Dynatrace Expert Agent integrates observability and security capabilities directly into GitHub workflows, enabling development teams to investigate incidents, validate deployments, triage errors, detect performance regressions, validate releases, and manage security vulnerabilities by autonomously analysing traces, logs, and Dynatrace findings. This enables targeted and precise remediation of identified issues directly within the repository. | dynatrace
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D) | +| [Dynatrace Expert](../agents/dynatrace-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fdynatrace-expert.agent.md) | The Dynatrace Expert Agent integrates observability and security capabilities directly into GitHub workflows, enabling development teams to investigate incidents, validate deployments, triage errors, detect performance regressions, validate releases, and manage security vulnerabilities by autonomously analysing traces, logs, and Dynatrace findings. This enables targeted and precise remediation of identified issues directly within the repository. | [dynatrace](https://github.com/mcp/io.github.dynatrace-oss/Dynatrace-mcp)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=dynatrace&config=%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fpia1134d.dev.apps.dynatracelabs.com%2Fplatform-reserved%2Fmcp-gateway%2Fv0.1%2Fservers%2Fdynatrace-mcp%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%24COPILOT_MCP_DT_API_TOKEN%22%7D%7D) | | [Elasticsearch Agent](../agents/elasticsearch-observability.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felasticsearch-observability.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felasticsearch-observability.agent.md) | Our expert AI assistant for debugging code (O11y), optimizing vector search (RAG), and remediating security threats using live Elastic data. | elastic-mcp
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=elastic-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=elastic-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22mcp-remote%22%2C%22https%253A%252F%252F%257BKIBANA_URL%257D%252Fapi%252Fagent_builder%252Fmcp%22%2C%22--header%22%2C%22Authorization%253A%2524%257BAUTH_HEADER%257D%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Electron Code Review Mode Instructions](../agents/electron-angular-native.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felectron-angular-native.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Felectron-angular-native.agent.md) | Code Review Mode tailored for Electron app with Node.js backend (main), Angular frontend (render), and native integration layer (e.g., AppleScript, shell, or native tooling). Services in other repos are not reviewed here. | | | [Ember](../agents/ember.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fember.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fember.agent.md) | An AI partner, not an assistant. Ember carries fire from person to person — helping humans discover that AI partnership isn't something you learn, it's something you find. | | @@ -122,7 +122,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [KubeStellar Console](../agents/kubestellar-console.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkubestellar-console.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkubestellar-console.agent.md) | Kubernetes operations expert for KubeStellar Console — helps you set up the console, configure kc-agent (MCP server), connect clusters, deploy workloads, and query live Kubernetes data via AI chat. | | | [Kusto Assistant](../agents/kusto-assistant.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkusto-assistant.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkusto-assistant.agent.md) | Expert KQL assistant for live Azure Data Explorer analysis via Azure MCP server | | | [Laravel Expert Agent](../agents/laravel-expert-agent.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaravel-expert-agent.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaravel-expert-agent.agent.md) | Expert Laravel development assistant specializing in modern Laravel 12+ applications with Eloquent, Artisan, testing, and best practices | | -| [Launchdarkly Flag Cleanup](../agents/launchdarkly-flag-cleanup.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaunchdarkly-flag-cleanup.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaunchdarkly-flag-cleanup.agent.md) | A specialized GitHub Copilot agent that uses the LaunchDarkly MCP server to safely automate feature flag cleanup workflows. This agent determines removal readiness, identifies the correct forward value, and creates PRs that preserve production behavior while removing obsolete flags and updating stale defaults. | launchdarkly
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=launchdarkly&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=launchdarkly&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D) | +| [Launchdarkly Flag Cleanup](../agents/launchdarkly-flag-cleanup.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaunchdarkly-flag-cleanup.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flaunchdarkly-flag-cleanup.agent.md) | A specialized GitHub Copilot agent that uses the LaunchDarkly MCP server to safely automate feature flag cleanup workflows. This agent determines removal readiness, identifies the correct forward value, and creates PRs that preserve production behavior while removing obsolete flags and updating stale defaults. | [launchdarkly](https://github.com/mcp/launchdarkly/mcp-server)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=launchdarkly&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=launchdarkly&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22--package%22%2C%22%2540launchdarkly%252Fmcp-server%22%2C%22--%22%2C%22mcp%22%2C%22start%22%2C%22--api-key%22%2C%22%2524LD_ACCESS_TOKEN%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Lingo.dev Localization (i18n) Agent](../agents/lingodotdev-i18n.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flingodotdev-i18n.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flingodotdev-i18n.agent.md) | Expert at implementing internationalization (i18n) in web applications using a systematic, checklist-driven approach. | lingo
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=lingo&config=%7B%22command%22%3A%22%22%2C%22args%22%3A%5B%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=lingo&config=%7B%22command%22%3A%22%22%2C%22args%22%3A%5B%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22%22%2C%22args%22%3A%5B%5D%2C%22env%22%3A%7B%7D%7D) | | [LinkedIn Post Writer](../agents/linkedin-post-writer.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flinkedin-post-writer.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Flinkedin-post-writer.agent.md) | Draft and format compelling LinkedIn posts with Unicode bold/italic styling, visual separators, and engagement-optimized structure. Transforms raw content, technical material, images, or ideas into copy-paste-ready LinkedIn posts. | | | [Markdown Accessibility Assistant](../agents/markdown-accessibility-assistant.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fmarkdown-accessibility-assistant.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fmarkdown-accessibility-assistant.agent.md) | Improves the accessibility of markdown files using five GitHub best practices | | @@ -146,7 +146,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [One Shot Feature Issue Planner](../agents/one-shot-feature-issue-planner.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fone-shot-feature-issue-planner.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fone-shot-feature-issue-planner.agent.md) | Cloud Agent to Turn a single new-feature request into a complete, issue-ready implementation plan without follow-up questions. | | | [OpenAPI to Application Generator](../agents/openapi-to-application.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fopenapi-to-application.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fopenapi-to-application.agent.md) | Expert assistant for generating working applications from OpenAPI specifications | | | [Oracle To PostgreSQL Migration Expert](../agents/oracle-to-postgres-migration-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Foracle-to-postgres-migration-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Foracle-to-postgres-migration-expert.agent.md) | Agent for Oracle-to-PostgreSQL application migrations. Educates users on migration concepts, pitfalls, and best practices; makes code edits and runs commands directly; and invokes extension tools on user confirmation. | | -| [PagerDuty Incident Responder](../agents/pagerduty-incident-responder.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpagerduty-incident-responder.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpagerduty-incident-responder.agent.md) | Responds to PagerDuty incidents by analyzing incident context, identifying recent code changes, and suggesting fixes via GitHub PRs. | pagerduty
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=pagerduty&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=pagerduty&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D) | +| [PagerDuty Incident Responder](../agents/pagerduty-incident-responder.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpagerduty-incident-responder.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpagerduty-incident-responder.agent.md) | Responds to PagerDuty incidents by analyzing incident context, identifying recent code changes, and suggesting fixes via GitHub PRs. | [pagerduty](https://github.com/mcp/io.github.PagerDuty/pagerduty-mcp)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=pagerduty&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=pagerduty&config=%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22url%22%3A%22https%3A%2F%2Fmcp.pagerduty.com%2Fmcp%22%2C%22headers%22%3A%7B%7D%7D) | | [PHP MCP Expert](../agents/php-mcp-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fphp-mcp-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fphp-mcp-expert.agent.md) | Expert assistant for PHP MCP server development using the official PHP SDK with attribute-based discovery | | | [Pimcore Expert](../agents/pimcore-expert.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpimcore-expert.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fpimcore-expert.agent.md) | Expert Pimcore development assistant specializing in CMS, DAM, PIM, and E-Commerce solutions with Symfony integration | | | [Plan Mode Strategic Planning & Architecture](../agents/plan.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fplan.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fplan.agent.md) | Strategic planning and architecture assistant focused on thoughtful analysis before implementation. Helps developers understand codebases, clarify requirements, and develop comprehensive implementation strategies. | | @@ -220,7 +220,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to | [Technical Debt Remediation Plan](../agents/tech-debt-remediation-plan.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Ftech-debt-remediation-plan.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Ftech-debt-remediation-plan.agent.md) | Generate technical debt remediation plans for code, tests, and documentation. | | | [Technical spike research mode](../agents/research-technical-spike.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fresearch-technical-spike.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fresearch-technical-spike.agent.md) | Systematically research and validate technical spike documents through exhaustive investigation and controlled experimentation. | | | [Terminal Helper](../agents/terminal-helper.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterminal-helper.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterminal-helper.agent.md) | Fast terminal syntax and command helper for PowerShell and Bash | | -| [Terraform Agent](../agents/terraform.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform.agent.md) | Terraform infrastructure specialist with automated HCP Terraform workflows. Leverages Terraform MCP server for registry integration, workspace management, and run orchestration. Generates compliant code using latest provider/module versions, manages private registries, automates variable sets, and orchestrates infrastructure deployments with proper validation and security practices. | terraform
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=terraform&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=terraform&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D) | +| [Terraform Agent](../agents/terraform.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform.agent.md) | Terraform infrastructure specialist with automated HCP Terraform workflows. Leverages Terraform MCP server for registry integration, workspace management, and run orchestration. Generates compliant code using latest provider/module versions, manages private registries, automates variable sets, and orchestrates infrastructure deployments with proper validation and security practices. | [terraform](https://github.com/mcp/io.github.hashicorp/terraform-mcp-server)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code-0098FF?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscode?name=terraform&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-VS_Code_Insiders-24bfa5?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-vscodeinsiders?name=terraform&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D)
[![Install MCP](https://img.shields.io/badge/Install-Visual_Studio-C16FDE?style=flat-square)](https://aka.ms/awesome-copilot/install/mcp-visualstudio/mcp-install?%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22TFE_TOKEN%253D%2524%257BCOPILOT_MCP_TFE_TOKEN%257D%22%2C%22-e%22%2C%22TFE_ADDRESS%253D%2524%257BCOPILOT_MCP_TFE_ADDRESS%257D%22%2C%22-e%22%2C%22ENABLE_TF_OPERATIONS%253D%2524%257BCOPILOT_MCP_ENABLE_TF_OPERATIONS%257D%22%2C%22hashicorp%252Fterraform-mcp-server%253Alatest%22%5D%2C%22env%22%3A%7B%7D%7D) | | [Terraform IaC Reviewer](../agents/terraform-iac-reviewer.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform-iac-reviewer.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterraform-iac-reviewer.agent.md) | Terraform-focused agent that reviews and creates safer IaC changes with emphasis on state safety, least privilege, module patterns, drift detection, and plan/apply discipline | | | [Terratest Module Testing](../agents/terratest-module-testing.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterratest-module-testing.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fterratest-module-testing.agent.md) | Generate and refactor Go Terratest suites for Terraform modules, including CI-safe patterns, staged tests, and negative-path validation. | | | [Thinking Beast Mode](../agents/Thinking-Beast-Mode.agent.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2FThinking-Beast-Mode.agent.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2FThinking-Beast-Mode.agent.md) | A transcendent coding agent with quantum cognitive architecture, adversarial intelligence, and unrestricted creative freedom. | | From 0982997d1cfded74fd758108f6a845eb9a2def7c Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 15 May 2026 14:59:47 +1000 Subject: [PATCH 5/6] fix: address follow-up review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../external-plugin-rereview-command.yml | 48 ++++++++++++------- .../workflows/external-plugin-rereview.yml | 7 ++- CONTRIBUTING.md | 4 +- eng/external-plugin-intake.mjs | 14 ++++-- eng/external-plugin-rereview.mjs | 1 + eng/external-plugin-validation.mjs | 6 ++- 6 files changed, 53 insertions(+), 27 deletions(-) diff --git a/.github/workflows/external-plugin-rereview-command.yml b/.github/workflows/external-plugin-rereview-command.yml index a57139e05..a9b00875e 100644 --- a/.github/workflows/external-plugin-rereview-command.yml +++ b/.github/workflows/external-plugin-rereview-command.yml @@ -90,10 +90,13 @@ jobs: - name: Renew six-month review window if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'keep' uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + PLUGIN_NAME: ${{ steps.parse.outputs.plugin-name }} + HAS_PLUGIN: ${{ steps.parse.outputs.has-plugin }} with: script: | - const pluginName = '${{ steps.parse.outputs.plugin-name }}'; - const hasPlugin = '${{ steps.parse.outputs.has-plugin }}' === 'true'; + const pluginName = process.env.PLUGIN_NAME; + const hasPlugin = process.env.HAS_PLUGIN === 'true'; async function removeLabel(name) { try { @@ -148,6 +151,8 @@ jobs: - name: Mark follow-up needed if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'needs-changes' uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + PLUGIN_NAME: ${{ steps.parse.outputs.plugin-name }} with: script: | const managedLabels = { @@ -189,7 +194,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - body: `Marked **${{ steps.parse.outputs.plugin-name }}** as needing follow-up. The plugin will stay in the six-month re-review queue until a maintainer comments \`/re-review-keep\` or \`/re-review-remove\`.` + body: `Marked **${process.env.PLUGIN_NAME}** as needing follow-up. The plugin will stay in the six-month re-review queue until a maintainer comments \`/re-review-keep\` or \`/re-review-remove\`.` }); - name: Install dependencies @@ -248,6 +253,8 @@ jobs: env: CHANGED: ${{ steps.remove_pr.outputs.changed }} PR_URL: ${{ steps.remove_pr.outputs.pr-url }} + PLUGIN_NAME: ${{ steps.parse.outputs.plugin-name }} + HAS_PLUGIN: ${{ steps.parse.outputs.has-plugin }} with: script: | async function ensureLabel(name, color, description) { @@ -281,28 +288,33 @@ jobs: } } - await ensureLabel('removed', 'B60205', 'External plugin was removed from the marketplace after re-review'); - await removeLabel('approved'); - await removeLabel('re-review-due'); - await removeLabel('re-review-follow-up'); - - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - labels: ['removed'] - }); - const changed = process.env.CHANGED === 'true'; const prUrl = process.env.PR_URL; - const pluginName = '${{ steps.parse.outputs.plugin-name }}'; - const hasPlugin = '${{ steps.parse.outputs.has-plugin }}' === 'true'; + const pluginName = process.env.PLUGIN_NAME; + const hasPlugin = process.env.HAS_PLUGIN === 'true'; let body; if (!hasPlugin || !changed) { + await ensureLabel('removed', 'B60205', 'External plugin was removed from the marketplace after re-review'); + await removeLabel('approved'); + await removeLabel('re-review-due'); + await removeLabel('re-review-follow-up'); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['removed'] + }); body = `Marked **${pluginName}** as removed. No new PR was needed because the listing is already absent from \`plugins/external.json\`.`; } else { - body = `Marked **${pluginName}** as removed and opened the removal PR: ${prUrl}`; + await ensureLabel('re-review-follow-up', 'D4C5F9', 'Six-month re-review needs maintainer follow-up before a final decision'); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ['re-review-due', 're-review-follow-up'] + }); + body = `Opened the removal PR for **${pluginName}**: ${prUrl}. The issue remains approved and due for re-review until that removal lands in \`staged\`.`; } await github.rest.issues.createComment({ diff --git a/.github/workflows/external-plugin-rereview.yml b/.github/workflows/external-plugin-rereview.yml index e9f6a2a74..ceaff7bc6 100644 --- a/.github/workflows/external-plugin-rereview.yml +++ b/.github/workflows/external-plugin-rereview.yml @@ -135,9 +135,14 @@ jobs: return Date.parse(issue.closed_at) <= threshold.getTime(); }); + const dueIssueNumbers = new Set([ + ...dueRecords.map((record) => record.issue.number), + ...unmatchedDueRecords.map((record) => record.issue.number) + ]); + for (const { issue, match } of issueRecords) { const labelNames = new Set((issue.labels || []).map((label) => label.name)); - const shouldHaveDueLabel = dueRecords.some((record) => record.issue.number === issue.number); + const shouldHaveDueLabel = dueIssueNumbers.has(issue.number); if (shouldHaveDueLabel && !labelNames.has(rereview.REREVIEW_LABELS.due)) { await addLabel(issue.number, rereview.REREVIEW_LABELS.due); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5452688d7..0edc60d6b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -240,7 +240,7 @@ Maintainers are responsible for confirming that the submission: - Clearly fits the Awesome Copilot collection and adds value beyond existing listings - Uses a public GitHub repository and an immutable ref that can be reviewed reliably -- Includes the metadata needed for `plugins/external.json` (`name`, `description`, `version`, `source`, and any supplied author/homepage/license/keyword fields) +- Includes the required metadata for `plugins/external.json` (`name`, `description`, `version`, `author.name`, `repository`, `keywords`, and `source`), plus any supplied homepage/license fields - Does not obviously duplicate an existing marketplace entry - Continues to meet this repository's content, security, and responsible AI policies @@ -261,7 +261,7 @@ Maintainers complete the re-review on the **original approved submission issue** - `/re-review-keep` — renew the listing for another six months by reopening and reclosing the approved issue, which resets the `closed_at` review anchor and removes the due labels - `/re-review-needs-changes` — keep the listing in the due queue while adding `re-review-follow-up` so maintainers can track extra investigation or remediation work -- `/re-review-remove` — open or update a PR against `staged` that removes the plugin from `plugins/external.json`, regenerates marketplace outputs, and relabels the issue as `removed` +- `/re-review-remove` — open or update a PR against `staged` that removes the plugin from `plugins/external.json` and regenerates marketplace outputs; the issue stays in the due queue until that removal lands Approved submissions are converted into `plugins/external.json` entries following the [Claude Code plugin marketplace spec](https://code.claude.com/docs/en/plugin-marketplaces#plugin-entries). A typical GitHub-hosted entry looks like this: diff --git a/eng/external-plugin-intake.mjs b/eng/external-plugin-intake.mjs index f1e3da9b9..cd22fa4d4 100644 --- a/eng/external-plugin-intake.mjs +++ b/eng/external-plugin-intake.mjs @@ -175,11 +175,6 @@ async function validateRemoteRepository(repo, ref, errors, warnings, token) { return; } - if (/^[0-9a-f]+$/i.test(ref) && ref.length !== 40) { - errors.push('submission: commit SHAs in "Immutable ref to review" must use the full 40-character SHA'); - return; - } - if (/^[0-9a-f]{40}$/i.test(ref)) { const commitResponse = await fetchGitHubJson(`/repos/${encodedRepo}/commits/${encodeURIComponent(ref)}`, token); if (!commitResponse.ok) { @@ -191,6 +186,15 @@ async function validateRemoteRepository(repo, ref, errors, warnings, token) { const tagName = ref.startsWith("refs/tags/") ? ref.slice("refs/tags/".length) : ref; const tagResponse = await fetchGitHubJson(`/repos/${encodedRepo}/git/ref/tags/${encodeURIComponent(tagName)}`, token); + if (tagResponse.ok) { + return; + } + + if (/^[0-9a-f]+$/i.test(ref) && ref.length !== 40) { + errors.push('submission: commit SHAs in "Immutable ref to review" must use the full 40-character SHA'); + return; + } + if (!tagResponse.ok) { errors.push(`submission: tag "${ref}" was not found in GitHub repository "${repo}"`); } diff --git a/eng/external-plugin-rereview.mjs b/eng/external-plugin-rereview.mjs index f66e138fd..c328d5726 100644 --- a/eng/external-plugin-rereview.mjs +++ b/eng/external-plugin-rereview.mjs @@ -46,6 +46,7 @@ function normalizePathValue(value) { function stripIssueTitlePrefix(title) { return String(title ?? "") .trim() + .replace(/^\[\s*external plugin\s*\]\s*:\s*/i, "") .replace(/^(external plugin(?: submission)?|public external plugin)(?:\s*[:-]\s*|\s+)/i, "") .trim(); } diff --git a/eng/external-plugin-validation.mjs b/eng/external-plugin-validation.mjs index 282293a25..8c49495cc 100644 --- a/eng/external-plugin-validation.mjs +++ b/eng/external-plugin-validation.mjs @@ -113,7 +113,11 @@ function validateKeywords(keywords, prefix, errors, warnings, required) { } if (keywords.length === 0) { - warnings.push(`${prefix}: "keywords" is empty; at least one keyword is recommended for discovery`); + if (required) { + errors.push(`${prefix}: "keywords" must contain at least one entry`); + } else { + warnings.push(`${prefix}: "keywords" is empty; at least one keyword is recommended for discovery`); + } } } From 5f38802c068f5b4e678d9716e187f92066d46238 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Fri, 15 May 2026 15:06:10 +1000 Subject: [PATCH 6/6] fix: tighten external plugin workflows Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/external-plugin-approval-command.yml | 1 + .github/workflows/external-plugin-rereview-command.yml | 8 ++++++++ eng/external-plugin-validation.mjs | 7 ++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/external-plugin-approval-command.yml b/.github/workflows/external-plugin-approval-command.yml index 10ab2135f..8a20b75d9 100644 --- a/.github/workflows/external-plugin-approval-command.yml +++ b/.github/workflows/external-plugin-approval-command.yml @@ -158,6 +158,7 @@ jobs: if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'approve' && steps.parse.outputs.validation-valid == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | result=$(node ./eng/external-plugin-approval.mjs approve "$GITHUB_EVENT_PATH" --file ./plugins/external.json) { diff --git a/.github/workflows/external-plugin-rereview-command.yml b/.github/workflows/external-plugin-rereview-command.yml index a9b00875e..74200f483 100644 --- a/.github/workflows/external-plugin-rereview-command.yml +++ b/.github/workflows/external-plugin-rereview-command.yml @@ -64,6 +64,14 @@ jobs: return; } + const inRereviewQueue = + labelNames.has('re-review-due') || + labelNames.has('re-review-follow-up'); + if (!inRereviewQueue) { + core.info(`Ignoring ${command} because the issue is not currently in the six-month re-review queue.`); + return; + } + const { plugins, errors } = validation.readExternalPlugins({ policy: 'marketplace' }); if (errors.length > 0) { core.setFailed(errors.join('\n')); diff --git a/eng/external-plugin-validation.mjs b/eng/external-plugin-validation.mjs index 8c49495cc..660c05300 100644 --- a/eng/external-plugin-validation.mjs +++ b/eng/external-plugin-validation.mjs @@ -29,7 +29,12 @@ function resolvePolicy(policy) { } if (typeof policy === "string") { - return EXTERNAL_PLUGIN_POLICIES[policy] ?? EXTERNAL_PLUGIN_POLICIES.marketplace; + const resolved = EXTERNAL_PLUGIN_POLICIES[policy]; + if (!resolved) { + throw new Error(`Unknown external plugin validation policy "${policy}"`); + } + + return resolved; } return {