Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fb74fd7
chore: publish from staged
github-actions[bot] May 26, 2026
42615db
chore: publish from staged
github-actions[bot] May 26, 2026
5551be5
chore: publish from staged
github-actions[bot] May 26, 2026
9e94c25
chore: publish from staged
github-actions[bot] May 26, 2026
75aef48
chore: publish from staged
github-actions[bot] May 27, 2026
9ff1151
chore: publish from staged
github-actions[bot] May 27, 2026
92249bc
chore: publish from staged
github-actions[bot] May 27, 2026
1979b1b
chore: publish from staged
github-actions[bot] May 27, 2026
b3ca113
chore: publish from staged
github-actions[bot] May 27, 2026
e9089dd
chore: publish from staged
github-actions[bot] May 27, 2026
1f4149a
chore: publish from staged
github-actions[bot] May 28, 2026
a3ee80a
chore: publish from staged
github-actions[bot] May 28, 2026
58d8291
chore: publish from staged
github-actions[bot] May 28, 2026
6941e57
chore: publish from staged
github-actions[bot] May 28, 2026
e19676b
chore: publish from staged
github-actions[bot] May 28, 2026
e544bc0
chore: publish from staged
github-actions[bot] May 28, 2026
2524d96
chore: publish from staged
github-actions[bot] May 28, 2026
a1ebf8a
chore: publish from staged
github-actions[bot] May 28, 2026
5323094
chore: publish from staged
github-actions[bot] May 28, 2026
b778365
chore: publish from staged
github-actions[bot] May 29, 2026
cc1000d
chore: publish from staged
github-actions[bot] May 29, 2026
b01ef70
chore: publish from staged
github-actions[bot] May 29, 2026
3d8e3d6
chore: publish from staged
github-actions[bot] May 29, 2026
1fd59ee
chore: publish from staged
github-actions[bot] Jun 2, 2026
2205f68
chore: publish from staged
github-actions[bot] Jun 4, 2026
62d44f1
chore: publish from staged
github-actions[bot] Jun 4, 2026
338bc5f
chore: publish from staged
github-actions[bot] Jun 5, 2026
3ae831f
Checkpoint from VS Code for cloud agent session
waashazig Jun 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@
{
"name": "modernize-dotnet",
"description": "AI-powered .NET modernization and upgrade assistant. Helps upgrade .NET Framework and .NET applications to the latest versions of .NET.",
"version": "1.0.1133-preview1",
"version": "1.0.1146-preview1",
"author": {
"name": "Microsoft",
"url": "https://www.microsoft.com"
Expand Down Expand Up @@ -603,7 +603,7 @@
"source": {
"source": "github",
"repo": "Avyayalaya/pm-skills-arsenal",
"ref": "refs/tags/v2.1.0"
"ref": "v2.1.0"
}
},
{
Expand Down Expand Up @@ -753,7 +753,7 @@
"source": {
"source": "github",
"repo": "SonarSource/sonarqube-agent-plugins",
"ref": "v2.0.0"
"ref": "2.0.0"
}
},
{
Expand Down Expand Up @@ -819,7 +819,7 @@
"source": "github",
"repo": "vercel/vercel-plugin",
"path": "/",
"ref": "6e51924cb249e2941de005d59f1ac6f768477b98"
"sha": "6e51924cb249e2941de005d59f1ac6f768477b98"
}
},
{
Expand Down
75 changes: 75 additions & 0 deletions .github/workflows/external-plugin-approval-command.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ name: External Plugin Approval Commands
on:
issue_comment:
types: [created]
pull_request:
types: [closed]

concurrency:
group: external-plugin-intake-${{ github.event.issue.number }}
cancel-in-progress: false

permissions:
contents: write
Expand All @@ -13,6 +19,7 @@ jobs:
handle-command:
runs-on: ubuntu-latest
if: >-
github.event_name == 'issue_comment' &&
!github.event.issue.pull_request &&
(contains(github.event.comment.body, '/approve') || contains(github.event.comment.body, '/reject'))
steps:
Expand Down Expand Up @@ -269,6 +276,10 @@ jobs:
color: '0E8A16',
description: 'Submission passed intake validation and is ready for maintainer review'
},
'requires-submitter-fixes': {
color: 'D93F0B',
description: 'Submission has quality-gate findings that submitter must fix before maintainer review'
},
'approved': {
color: '1D76DB',
description: 'Submission was approved by a maintainer'
Expand Down Expand Up @@ -407,6 +418,65 @@ jobs:
});
}

sync-merged-pr-labels:
runs-on: ubuntu-latest
if: >-
github.event_name == 'pull_request' &&
github.event.action == 'closed' &&
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'external-plugin')
steps:
- name: Normalize merged external plugin PR labels
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const prNumber = context.payload.pull_request.number;
const staleLabels = ['awaiting-review', 'awaiting-approval', 'ready-for-review', 'rejected'];

try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'approved',
color: '1D76DB',
description: 'Submission was approved by a maintainer'
});
} catch (error) {
if (error.status !== 422) {
throw error;
}
}

const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100
});
const labelNames = new Set(currentLabels.map((label) => label.name));

if (!labelNames.has('approved')) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: ['approved']
});
}

for (const labelName of staleLabels) {
if (!labelNames.has(labelName)) {
continue;
}

await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: labelName
});
}

- name: Finalize rejection
if: steps.parse.outputs.should-run == 'true' && steps.parse.outputs.command == 'reject'
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
Expand All @@ -428,6 +498,10 @@ jobs:
color: '0E8A16',
description: 'Submission passed intake validation and is ready for maintainer review'
},
'requires-submitter-fixes': {
color: 'D93F0B',
description: 'Submission has quality-gate findings that submitter must fix before maintainer review'
},
'approved': {
color: '1D76DB',
description: 'Submission was approved by a maintainer'
Expand Down Expand Up @@ -479,6 +553,7 @@ jobs:

await removeLabel('awaiting-review');
await removeLabel('ready-for-review');
await removeLabel('requires-submitter-fixes');
await removeLabel('approved');

const marker = '<!-- external-plugin-rejection -->';
Expand Down
121 changes: 101 additions & 20 deletions .github/workflows/external-plugin-intake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,67 +13,148 @@ permissions:
issues: write

jobs:
validate-submission:
evaluate-submission:
runs-on: ubuntu-latest
if: >-
contains(github.event.issue.labels.*.name, 'external-plugin') ||
contains(github.event.issue.body, '<!-- external-plugin-submission -->')
outputs:
evaluation: ${{ steps.evaluation.outputs.result }}
should-sync: ${{ steps.guard.outputs.should-sync }}
issue-state: ${{ steps.guard.outputs.issue-state }}
issue-action: ${{ steps.guard.outputs.issue-action }}
issue-labels: ${{ steps.guard.outputs.issue-labels }}
plugin-json: ${{ steps.evaluation.outputs.plugin-json }}
valid: ${{ steps.evaluation.outputs.valid }}
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: staged

- name: Evaluate issue guard rails
id: guard
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const issueState = context.payload.issue.state;
const action = context.payload.action;
const labels = (context.payload.issue.labels || []).map((label) => label.name);
const isApproved = labels.includes('approved');
const isClosedWithoutReopen = issueState === 'closed' && action !== 'reopened';

core.setOutput('issue-state', issueState);
core.setOutput('issue-action', action);
core.setOutput('issue-labels', JSON.stringify(labels));
core.setOutput('should-sync', (!isApproved && !isClosedWithoutReopen) ? 'true' : 'false');

- name: Evaluate submission
id: evaluation
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
result=$(node ./eng/external-plugin-intake.mjs "$GITHUB_EVENT_PATH")
result=$(node ./eng/external-plugin-intake.mjs "$GITHUB_EVENT_PATH" "${{ github.run_id }}" "${{ github.repository_owner }}" "${{ github.event.repository.name }}")
{
echo 'result<<EOF'
echo "$result"
echo 'EOF'
} >> "$GITHUB_OUTPUT"

- name: Sync labels and comment
valid=$(node -e "const data = JSON.parse(process.argv[1]); process.stdout.write(data.valid ? 'true' : 'false');" "$result")
plugin=$(node -e "const data = JSON.parse(process.argv[1]); process.stdout.write(JSON.stringify(data.plugin || {}));" "$result")
echo "valid=$valid" >> "$GITHUB_OUTPUT"
{
echo 'plugin-json<<EOF'
echo "$plugin"
echo 'EOF'
} >> "$GITHUB_OUTPUT"

quality-gates:
needs: evaluate-submission
if: >-
needs.evaluate-submission.outputs.should-sync == 'true' &&
needs.evaluate-submission.outputs.valid == 'true'
uses: ./.github/workflows/external-plugin-quality-gates.yml
with:
plugin-json: ${{ needs.evaluate-submission.outputs.plugin-json }}

sync-state:
runs-on: ubuntu-latest
needs: [evaluate-submission, quality-gates]
if: always() && needs.evaluate-submission.outputs.should-sync == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: staged

- name: Merge evaluation and sync labels/comments
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
env:
RESULT_JSON: ${{ steps.evaluation.outputs.result }}
BASE_RESULT_JSON: ${{ needs.evaluate-submission.outputs.evaluation }}
BASE_VALID: ${{ needs.evaluate-submission.outputs.valid }}
QUALITY_RESULT_JSON: ${{ needs.quality-gates.outputs.quality-result }}
QUALITY_JOB_RESULT: ${{ needs.quality-gates.result }}
ISSUE_STATE: ${{ needs.evaluate-submission.outputs.issue-state }}
ISSUE_LABELS: ${{ needs.evaluate-submission.outputs.issue-labels }}
with:
script: |
const path = require('path');
const { pathToFileURL } = require('url');

const intake = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-intake.mjs')).href);
const intakeState = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-intake-state.mjs')).href);

const 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);
const baseResult = JSON.parse(process.env.BASE_RESULT_JSON);
let finalResult = baseResult;

if (existingLabelNames.includes('approved')) {
core.info('Issue is already approved; skipping intake synchronization.');
return;
}
if (process.env.BASE_VALID === 'true') {
let qualityResult;
if (process.env.QUALITY_JOB_RESULT === 'failure' || process.env.QUALITY_JOB_RESULT === 'cancelled') {
qualityResult = {
overall_status: 'infra_error',
skill_validator_status: 'infra_error',
smoke_status: 'infra_error',
failure_class: 'infra',
summary: 'Quality-gate workflow failed unexpectedly. Re-run intake to retry.',
};
} else if (process.env.QUALITY_RESULT_JSON) {
qualityResult = JSON.parse(process.env.QUALITY_RESULT_JSON);
} else {
qualityResult = {
overall_status: 'infra_error',
skill_validator_status: 'infra_error',
smoke_status: 'infra_error',
failure_class: 'infra',
summary: 'Quality-gate workflow did not return results. Re-run intake to retry.',
};
}

if (issueState === 'closed' && action !== 'reopened') {
core.info('Issue is closed; waiting for reopen before rerunning intake synchronization.');
return;
finalResult = intake.applyQualityGateResult(baseResult, qualityResult, context.runId, context.repo.owner, context.repo.repo);
}

await intakeState.applyExternalPluginIntakeEvaluation({
github,
owner: context.repo.owner,
repo: context.repo.repo,
issueNumber,
evaluation: result
issueNumber: context.issue.number,
evaluation: finalResult
});

if (!result.valid && issueState === 'open') {
const issueState = process.env.ISSUE_STATE;
const labels = new Set(JSON.parse(process.env.ISSUE_LABELS || '[]'));
if (finalResult.intakeState === 'rejected' && issueState === 'open') {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
issue_number: context.issue.number,
state: 'closed'
});
} else if (finalResult.intakeState !== 'rejected' && issueState === 'closed' && labels.has('rejected')) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'open'
});
}
Loading
Loading