Skip to content

Migrate plugins and canvas extensions to Agent Plugins spec - #2546

Open
aaronpowell wants to merge 8 commits into
mainfrom
aaronpowell-plugin-spec-compliance-review
Open

Migrate plugins and canvas extensions to Agent Plugins spec#2546
aaronpowell wants to merge 8 commits into
mainfrom
aaronpowell-plugin-spec-compliance-review

Conversation

@aaronpowell

@aaronpowell aaronpowell commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Pull Request Checklist

  • I have read and followed the CONTRIBUTING.md guidelines.
  • I have read and followed the Guidance for submissions involving paid services.
  • My contribution adds a new instruction, prompt, agent, skill, workflow, or canvas extension file in the correct directory.
  • The file follows the required naming convention.
  • The content is clearly structured and follows the example format.
  • I have tested my instructions, prompt, agent, skill, workflow, or canvas extension with GitHub Copilot.
  • I have run npm start and verified that README.md is up to date.
  • I am targeting the main branch for this pull request.

Description

This PR migrates curated plugins and canvas extensions to the Agent Plugins v1.0.0 manifest shape and removes the old standalone-extension model. Canvas implementations remain reusable sources under extensions/, while all shippable manifests live under plugins/.

The build now materializes reusable extensions into any plugin that references them through build-only .github/plugin/extensions.json files, without adding custom fields to plugin.json. Canvas extension manifests use the GitHub Copilot client namespace com.github.copilot, and marketplace/website generation no longer treats extensions/ as a separate installable catalog.

It also adds blocking internal validation, non-blocking external spec compliance reporting, a repo-local canvas extension scaffolding skill, and stable PR-comment updates for validation failures.

Validation

  • npm run plugin:validate
  • npm run build
  • npm run website:data
  • npm run skill:validate

Type of Contribution

  • New instruction file.
  • New prompt file.
  • New agent file.
  • New plugin.
  • New skill file.
  • New agentic workflow.
  • New canvas extension.
  • Update to existing instruction, prompt, agent, plugin, skill, workflow, or canvas extension.
  • Other (please specify):

Additional Notes

The source manifests retain build-time composition information needed to materialize agents and skills; served marketplace manifests are stripped to Agent Plugins spec fields. Canvas extension source can be reused by multiple plugins without duplicating the implementation.


By submitting this pull request, I confirm that my contribution abides by the Code of Conduct and will be licensed under the MIT License.

aaronpowell and others added 4 commits July 30, 2026 10:56
- Add \ to all 69 curated plugin manifests
- Migrate all 18 extension manifests: add \, move logo into
  �xtensions.com.github.copilot.logo namespace, remove top-level
  logo and string �xtensions: '.'
- Update eng/validate-plugins.mjs: require \, validate
  namespace-keyed extensions object for canvas extensions, widen
  name pattern to allow dots (spec §5.5, max 64 chars)
- Update eng/materialize-plugins.mjs: emit spec-clean served manifests
  (only spec fields: \, name, version, description, author,
  homepage, repository, license, keywords, extensions)
- Update eng/generate-website-data.mjs: read logo from namespace
  with fallback to top-level logo for compatibility
- Update eng/create-plugin.mjs: scaffold emits \
- Add .github/workflows/validate-plugins.yml: blocking CI for PRs
  touching plugins/** or extensions/**
- Add spec compliance check to external plugin quality gates:
  non-blocking warnings with ✅/⚠️/🛑 emoji legend
- Update AGENTS.md: document new extension manifest shape,
  add \ to plugin checklist

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f3a88cb-e01e-4760-8125-460490dc1a76
- Move all extension plugin manifests from extensions/<name> to plugins/<name>
- Keep extensions/<name> as reusable source only
- Remove standalone extension discovery from marketplace and website plugin catalogs
- Auto-bundle same-name extension sources during materialization
- Add build-only extensions.json references for sharing extensions across plugins
- Remove x-awesome-copilot extension metadata support
- Update validation and contributor documentation

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f3a88cb-e01e-4760-8125-460490dc1a76
- Add repo-local skill for creating canvas extension sources
- Generate spec-compliant plugin manifests under plugins/
- Support registering reusable extensions with multiple plugins
- Remove guidance for extension-local plugin manifests and custom fields

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f3a88cb-e01e-4760-8125-460490dc1a76
- Use each extension ID as its manifest namespace key
- Update validation and website generation to resolve extension-specific namespaces
- Upsert plugin validation PR comments using the existing repository pattern

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f3a88cb-e01e-4760-8125-460490dc1a76
Copilot AI balanced review requested due to automatic review settings August 5, 2026 01:09
@aaronpowell
aaronpowell requested a review from dvelton as a code owner August 5, 2026 01:09
@github-actions github-actions Bot added new-submission PR adds at least one new contribution plugin PR touches plugins workflow PR touches workflow automation labels Aug 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates curated plugins and canvas extensions to Agent Plugins v1.0.0, centralizing installable manifests under plugins/ and updating validation, materialization, marketplace, website, and contribution tooling.

Changes:

  • Adds v1.0.0 schemas and namespace-based canvas metadata.
  • Materializes reusable extension sources through extensions.json.
  • Adds internal/external validation workflows, documentation, and scaffolding guidance.
Show a summary per file
File Description
plugins/work-hub/.github/plugin/plugin.json Migrates canvas manifest.
plugins/where-was-i/.github/plugin/plugin.json Migrates canvas manifest.
plugins/visual-pr/.github/plugin/plugin.json Adds v1 schema.
plugins/typespec-m365-copilot/.github/plugin/plugin.json Adds v1 schema.
plugins/typescript-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/token-pacman/.github/plugin/plugin.json Migrates canvas manifest.
plugins/tiny-tool-town-submitter/.github/plugin/plugin.json Migrates canvas manifest.
plugins/testing-automation/.github/plugin/plugin.json Adds v1 schema.
plugins/technical-spike/.github/plugin/plugin.json Adds v1 schema.
plugins/swift-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/structured-autonomy/.github/plugin/plugin.json Adds v1 schema.
plugins/software-engineering-team/.github/plugin/plugin.json Adds v1 schema.
plugins/skill-image-gen/.github/plugin/plugin.json Adds v1 schema.
plugins/site-studio/.github/plugin/plugin.json Migrates canvas manifest.
plugins/security-best-practices/.github/plugin/plugin.json Adds v1 schema.
plugins/salesforce-development/.github/plugin/plugin.json Adds v1 schema.
plugins/rust-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/rug-agentic-workflow/.github/plugin/plugin.json Adds v1 schema.
plugins/ruby-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/roundup/.github/plugin/plugin.json Adds v1 schema.
plugins/repo-actions-hub/.github/plugin/plugin.json Migrates canvas manifest.
plugins/release-notes-showcase/.github/plugin/plugin.json Migrates canvas manifest.
plugins/react19-upgrade/.github/plugin/plugin.json Adds v1 schema.
plugins/react18-upgrade/.github/plugin/plugin.json Adds v1 schema.
plugins/python-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/project-planning/.github/plugin/plugin.json Adds v1 schema.
plugins/project-documenter/.github/plugin/plugin.json Adds v1 schema.
plugins/power-platform-mcp-connector-development/.github/plugin/plugin.json Adds v1 schema.
plugins/power-platform-architect/.github/plugin/plugin.json Adds v1 schema.
plugins/power-bi-development/.github/plugin/plugin.json Adds v1 schema.
plugins/power-apps-code-apps/.github/plugin/plugin.json Adds v1 schema.
plugins/php-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/phoenix/.github/plugin/plugin.json Adds v1 schema.
plugins/pcf-development/.github/plugin/plugin.json Adds v1 schema.
plugins/partners/.github/plugin/plugin.json Adds v1 schema.
plugins/ospo-sponsorship/.github/plugin/plugin.json Adds v1 schema.
plugins/oracle-to-postgres-migration-expert/.github/plugin/plugin.json Adds v1 schema.
plugins/openapi-to-application-python-fastapi/.github/plugin/plugin.json Adds v1 schema.
plugins/openapi-to-application-nodejs-nestjs/.github/plugin/plugin.json Adds v1 schema.
plugins/openapi-to-application-java-spring-boot/.github/plugin/plugin.json Adds v1 schema.
plugins/openapi-to-application-go/.github/plugin/plugin.json Adds v1 schema.
plugins/openapi-to-application-csharp-dotnet/.github/plugin/plugin.json Adds v1 schema.
plugins/noob-mode/.github/plugin/plugin.json Adds v1 schema.
plugins/napkin/.github/plugin/plugin.json Adds v1 schema.
plugins/mcp-m365-copilot/.github/plugin/plugin.json Adds v1 schema.
plugins/kotlin-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/java-modernization-studio/.github/plugin/plugin.json Migrates canvas manifest.
plugins/java-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/java-development/.github/plugin/plugin.json Adds v1 schema.
plugins/go-mcp-development/.github/plugin/plugin.json Adds v1 schema.
plugins/gesture-review/.github/plugin/plugin.json Migrates canvas manifest.
plugins/gem-team/.github/plugin/plugin.json Adds v1 schema.
plugins/frontend-web-dev/.github/plugin/plugin.json Adds v1 schema.
plugins/flowstudio-power-automate/.github/plugin/plugin.json Adds v1 schema.
plugins/feedback-themes/.github/plugin/plugin.json Migrates canvas manifest.
plugins/fastah-ip-geo-tools/.github/plugin/plugin.json Adds v1 schema.
plugins/eyeball/.github/plugin/plugin.json Adds v1 schema.
plugins/ember/.github/plugin/plugin.json Adds v1 schema.
plugins/edge-ai-tasks/.github/plugin/plugin.json Adds v1 schema.
plugins/doublecheck/.github/plugin/plugin.json Adds v1 schema.
plugins/diagram-viewer/.github/plugin/plugin.json Migrates canvas manifest.
plugins/devops-oncall/.github/plugin/plugin.json Adds v1 schema.
plugins/dataverse-sdk-for-python/.github/plugin/plugin.json Adds v1 schema.
plugins/database-data-management/.github/plugin/plugin.json Adds v1 schema.
plugins/csharp-dotnet-development/.github/plugin/plugin.json Adds v1 schema.
plugins/copilot-sdk/.github/plugin/plugin.json Adds v1 schema.
plugins/convert-to-md/.github/plugin/plugin.json Adds v1 schema.
plugins/context-matic/.github/plugin/plugin.json Adds v1 schema.
plugins/context-engineering/.github/plugin/plugin.json Adds v1 schema.
plugins/connector-namespaces/.github/plugin/plugin.json Migrates canvas manifest.
plugins/color-orb/.github/plugin/plugin.json Migrates canvas manifest.
plugins/cms-development/.github/plugin/plugin.json Adds v1 schema.
plugins/clojure-interactive-programming/.github/plugin/plugin.json Adds v1 schema.
plugins/chromium-control-canvas/.github/plugin/plugin.json Migrates canvas manifest.
plugins/cast-imaging/.github/plugin/plugin.json Adds v1 schema.
plugins/backlog-swipe-triage/.github/plugin/plugin.json Migrates canvas manifest.
plugins/azure-cloud-development/.github/plugin/plugin.json Adds v1 schema.
plugins/aws-cloud-development/.github/plugin/plugin.json Adds v1 schema.
plugins/awesome-copilot/.github/plugin/plugin.json Adds v1 schema.
plugins/automate-this/.github/plugin/plugin.json Adds v1 schema.
plugins/arize-ax/.github/plugin/plugin.json Adds v1 schema.
plugins/arch/.github/plugin/plugin.json Adds v1 schema.
plugins/arcade-canvas/.github/plugin/plugin.json Migrates canvas manifest.
plugins/apng-studio/.github/plugin/plugin.json Migrates canvas manifest.
plugins/ai-team-orchestration/.github/plugin/plugin.json Adds v1 schema.
plugins/acreadiness-cockpit/.github/plugin/plugin.json Adds v1 schema.
plugins/accessibility-kanban/.github/plugin/plugin.json Migrates canvas manifest.
eng/validate-plugins.mjs Updates internal manifest and extension validation.
eng/materialize-plugins.mjs Materializes reusable extensions and served manifests.
eng/generate-website-data.mjs Sources extension metadata from plugins.
eng/generate-marketplace.mjs Removes standalone extension catalog generation.
eng/external-plugin-quality-gates.mjs Adds non-blocking spec checks.
eng/external-plugin-pr-quality-gates.mjs Reports external spec status.
eng/external-plugin-intake.mjs Formats quality-gate statuses and warnings.
eng/create-plugin.mjs Adds schema to generated manifests.
docs/README.plugins.md Adds migrated canvas plugins to documentation.
CONTRIBUTING.md Documents the new extension model.
AGENTS.md Updates repository development guidance.
.github/workflows/validate-plugins.yml Adds plugin validation and PR comments.
.github/workflows/external-plugin-pr-quality-gates.yml Reports spec compliance in PR checks.
.github/workflows/external-plugin-intake.yml Updates intake fallback statuses.
.github/workflows/external-plugin-command-router.yml Updates routed intake fallback statuses.
.github/skills/create-canvas-extension/SKILL.md Adds canvas-extension scaffolding guidance.
.github/plugin/marketplace.json Redirects canvas entries to plugin sources.

Review details

Suppressed comments (18)

plugins/accessibility-kanban/.github/plugin/plugin.json:19

  • accessibility-kanban is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/apng-studio/.github/plugin/plugin.json:19
  • apng-studio is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/backlog-swipe-triage/.github/plugin/plugin.json:19
  • backlog-swipe-triage is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/chromium-control-canvas/.github/plugin/plugin.json:20
  • chromium-control-canvas is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/color-orb/.github/plugin/plugin.json:19
  • color-orb is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/connector-namespaces/.github/plugin/plugin.json:19
  • connector-namespaces is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/diagram-viewer/.github/plugin/plugin.json:19
  • diagram-viewer is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/feedback-themes/.github/plugin/plugin.json:19
  • feedback-themes is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/arcade-canvas/.github/plugin/plugin.json:19
  • arcade-canvas is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/gesture-review/.github/plugin/plugin.json:19
  • gesture-review is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/java-modernization-studio/.github/plugin/plugin.json:20
  • java-modernization-studio is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/release-notes-showcase/.github/plugin/plugin.json:19
  • release-notes-showcase is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/repo-actions-hub/.github/plugin/plugin.json:19
  • repo-actions-hub is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/site-studio/.github/plugin/plugin.json:19
  • site-studio is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/tiny-tool-town-submitter/.github/plugin/plugin.json:19
  • tiny-tool-town-submitter is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/token-pacman/.github/plugin/plugin.json:19
  • token-pacman is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/where-was-i/.github/plugin/plugin.json:19
  • where-was-i is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
    plugins/work-hub/.github/plugin/plugin.json:19
  • work-hub is not a reverse-domain extension namespace. Use the documented Copilot namespace so this manifest matches Agent Plugins v1.0.0 semantics and the scaffolding guidance.
  • Files reviewed: 104/104 changed files
  • Comments generated: 11
  • Review effort level: Balanced

return candidate;
}

function inspectAgentPluginSpecCompliance(pluginRoot) {
"keywords",
"extensions",
]);
const AGENT_PLUGIN_NAME_PATTERN = /^(?!.*(?:--|\\.\\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
Comment thread eng/validate-plugins.mjs
}
if (!/^[a-z0-9-]+$/.test(name)) {
errors.push("name must contain only lowercase letters, numbers, and hyphens");
if (!/^[a-z0-9][a-z0-9.-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {
Comment thread eng/validate-plugins.mjs
Comment on lines +33 to +35
function validateSchema(parsed) {
if (parsed["$schema"] !== AGENT_PLUGINS_SCHEMA) {
return `$schema must be "${AGENT_PLUGINS_SCHEMA}"`;
Comment thread eng/validate-plugins.mjs
Comment thread eng/validate-plugins.mjs
Comment on lines +217 to 219
if (!fs.existsSync(readmePath) && !isExtensionPlugin) {
errors.push("missing required file: README.md");
}
Comment on lines +6 to +8
paths:
- "plugins/**"
- "extensions/**"
Comment on lines +35 to +36
- name: Post failure summary on PR
if: failure() && steps.validate.outcome == 'failure'
Comment thread docs/README.plugins.md

| Name | Description | Items | Tags |
| ---- | ----------- | ----- | ---- |
| [accessibility-kanban](../plugins/accessibility-kanban/README.md) | Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work. | 0 items | accessibility, github-issues, issue-triage, kanban-board, planning-workflow, status-tracking |
'All internal plugins and extensions must include:',
'- `"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"` in `plugin.json`',
'- A valid `name`, `description`, and `version`',
'- For **extensions**: `extensions["<extension-id>"].logo` set to `"assets/preview.png"`',
- Adopt com.github.copilot for all canvas extension manifests
- Require the namespace during validation and website generation
- Update extension scaffolding guidance

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f3a88cb-e01e-4760-8125-460490dc1a76
Copilot AI review requested due to automatic review settings August 5, 2026 01:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (7)

eng/external-plugin-quality-gates.mjs:168

  • inspectAgentPluginSpecCompliance is declared inside the for block of findPluginJson, so it is not in scope where runExternalPluginQualityGates calls it. Every external-plugin quality run that reaches this call will throw a ReferenceError and be reported as an infrastructure failure instead of running any gates. Move this function to module scope after findPluginJson.
    function inspectAgentPluginSpecCompliance(pluginRoot) {

eng/validate-plugins.mjs:24

  • This pattern does not enforce the v1.0.0 prohibition on consecutive -- or .., so names such as foo--bar and foo..bar pass internal validation even though the referenced schema rejects them. Use the schema's negative lookahead here.
  if (!/^[a-z0-9][a-z0-9.-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {

eng/external-plugin-quality-gates.mjs:24

  • The dots are over-escaped in this regex literal: \\.\\. matches a backslash-containing sequence rather than two literal dots. As a result, an external manifest named foo..bar is incorrectly reported as spec-compliant. Use \.\. in the regex literal, matching the v1.0.0 schema pattern.
const AGENT_PLUGIN_NAME_PATTERN = /^(?!.*(?:--|\\.\\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;

eng/validate-plugins.mjs:217

  • Extension plugins are now exempted from the repository-wide README requirement, but the documentation generator unconditionally links every plugin to ../plugins/${dir}/README.md (eng/update-readme.mjs:795). The migrated extension plugin folders have no README, so all newly generated links in docs/README.plugins.md are broken. Keep README mandatory for plugin directories and add the missing extension-plugin READMEs, or update the generator to support a real alternative target.
  if (!fs.existsSync(readmePath) && !isExtensionPlugin) {

.github/workflows/validate-plugins.yml:50

  • This failure guidance names the namespace as <extension-id>, but validation and every migrated manifest require the fixed client namespace com.github.copilot. Following the posted remediation would still fail validation; show the actual key.
              '- For **extensions**: `extensions["<extension-id>"].logo` set to `"assets/preview.png"`',

docs/README.plugins.md:30

  • The migrated canvas plugin contains one extension, but this generated row reports 0 items; the same incorrect count appears on every newly added canvas-plugin row. Update the README generator to include the plugin's implicit/self extension (and any extensions.json references), then regenerate this file so catalog counts remain accurate.
| [accessibility-kanban](../plugins/accessibility-kanban/README.md) | Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work. | 0 items | accessibility, github-issues, issue-triage, kanban-board, planning-workflow, status-tracking |

eng/materialize-plugins.mjs:169

  • The served manifest declares assets/preview.png, but materialization copies the extension to plugins/<plugin>/extensions/<name>/, leaving the declared asset absent from the plugin root (the migrated plugin directories currently contain only .github). Installed canvas plugins will therefore reference a logo path that is not packaged at that location. Copy the standalone extension's preview into the manifest-relative assets/preview.png path while retaining the extension source under extensions/.
      totalExtensions++;
  • Files reviewed: 104/104 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Resolve upstream changes while preserving Agent Plugins migration, reusable canvas extension bundling, and external plugin quality reporting.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f3a88cb-e01e-4760-8125-460490dc1a76
Copilot AI review requested due to automatic review settings August 5, 2026 01:21
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8f3a88cb-e01e-4760-8125-460490dc1a76
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🔒 PR Risk Scan Results

Scanned 114 changed file(s).

Severity Count
🔴 High 0
🟠 Medium 3
ℹ️ Info 0
Severity Rule File Line Match
🟠 package-exec-command eng/README.md 45 - Purpose: detect missing contributors, infer contribution types from their merged PR files, and run `npx all-contributors add ...` to update `.all-contributorsrc`.
🟠 unpinned-version-indicator eng/update-readme.mjs 515 hook.assets.length &gt; 0
🟠 unpinned-version-indicator eng/update-readme.mjs 629 skill.assets.length &gt; 0

This is an automated soft-gate report. Findings indicate review targets and do not block merge by themselves.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (5)

eng/external-plugin-quality-gates.mjs:186

  • inspectAgentPluginSpecCompliance is declared inside the for block of findPluginJson, so it is out of scope at the call on line 990. Every valid external-plugin run reaches that call, throws a ReferenceError, and is reported as an infrastructure failure. Move this function to module scope.
    function inspectAgentPluginSpecCompliance(pluginRoot) {

eng/materialize-plugins.mjs:1

  • The new extension-copy path only places assets/preview.png under extensions/<name>/assets, while the served manifest still declares extensions.com.github.copilot.logo as root-relative assets/preview.png. The source plugin directories do not contain that root asset (for example, plugins/apng-studio/assets/preview.png is absent), so published extension plugins will have a broken logo. Copy each extension's preview asset to the plugin root during materialization, as the removed implementation did.
#!/usr/bin/env nodeimport fs from "fs";import path from "path";import { ROOT_FOLDER } from "./constants.mjs";const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions");/** * Recursively copy a directory. */function copyDirRecursive(src, dest) {  fs.mkdirSync(dest, { recursive: true });  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {    const srcPath = path.join(src, entry.name);    const destPath = path.join(dest, entry.name);    if (entry.isDirectory()) {      copyDirRecursive(srcPath, destPath);    } else {      fs.copyFileSync(srcPath, destPath);    }  }}/** * Resolve a plugin-relative path to the repo-root source file. * *   ./agents/foo.md   → ROOT/agents/foo.agent.md *   ./skills/baz/      → ROOT/skills/baz/ */function resolveSource(relPath) {  const basename = path.basename(relPath, ".md");  if (relPath.startsWith("./agents/")) {    return path.join(ROOT_FOLDER, "agents", `${basename}.agent.md`);  }  if (relPath.startsWith("./skills/")) {    // Strip trailing slash and get the skill folder name    const skillName = relPath.replace(/^\.\/skills\//, "").replace(/\/$/, "");    return path.join(ROOT_FOLDER, "skills", skillName);  }  if (relPath.startsWith("./extensions/")) {    const extensionName = relPath.replace(/^\.\/extensions\//, "").replace(/\/$/, "");    return path.join(ROOT_FOLDER, "extensions", extensionName);  }  return null;}function readExtensionReferences(pluginPath, pluginName) {  const referencesPath = path.join(pluginPath, ".github/plugin/extensions.json");  let references = [];  if (fs.existsSync(referencesPath)) {    try {      references = JSON.parse(fs.readFileSync(referencesPath, "utf8"));    } catch (err) {      throw new Error(`Failed to parse ${referencesPath}: ${err.message}`);    }  }  if (!Array.isArray(references) || references.some((name) => typeof name !== "string")) {    throw new Error(`${referencesPath} must contain an array of extension names`);  }  const names = new Set(references);  if (fs.existsSync(path.join(EXTENSIONS_DIR, pluginName, "extension.mjs"))) {    names.add(pluginName);  }  return [...names].sort();}function materializePlugins() {  console.log("Materializing plugin files...\n");  if (!fs.existsSync(PLUGINS_DIR)) {    console.error(`Error: Plugins directory not found at ${PLUGINS_DIR}`);    process.exit(1);  }  const pluginDirs = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true })    .filter(entry => entry.isDirectory())    .map(entry => entry.name)    .sort();  let totalAgents = 0;  let totalSkills = 0;  let totalExtensions = 0;  let warnings = 0;  let errors = 0;  for (const dirName of pluginDirs) {    const pluginPath = path.join(PLUGINS_DIR, dirName);    const pluginJsonPath = path.join(pluginPath, ".github/plugin", "plugin.json");    if (!fs.existsSync(pluginJsonPath)) {      continue;    }    let metadata;    try {      metadata = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));    } catch (err) {      console.error(`Error: Failed to parse ${pluginJsonPath}: ${err.message}`);      errors++;      continue;    }    const pluginName = metadata.name || dirName;    // Process agents    if (Array.isArray(metadata.agents)) {      for (const relPath of metadata.agents) {        const src = resolveSource(relPath);        if (!src) {          console.warn(`  ⚠ ${pluginName}: Unknown path format: ${relPath}`);          warnings++;          continue;        }        if (!fs.existsSync(src)) {          console.warn(`  ⚠ ${pluginName}: Source not found: ${src}`);          warnings++;          continue;        }        const dest = path.join(pluginPath, relPath.replace(/^\.\//, ""));        fs.mkdirSync(path.dirname(dest), { recursive: true });        fs.copyFileSync(src, dest);        totalAgents++;      }    }    // Process skills    if (Array.isArray(metadata.skills)) {      for (const relPath of metadata.skills) {        const src = resolveSource(relPath);        if (!src) {          console.warn(`  ⚠ ${pluginName}: Unknown path format: ${relPath}`);          warnings++;          continue;        }        if (!fs.existsSync(src) || !fs.statSync(src).isDirectory()) {          console.warn(`  ⚠ ${pluginName}: Source directory not found: ${src}`);          warnings++;          continue;        }        const dest = path.join(pluginPath, relPath.replace(/^\.\//, "").replace(/\/$/, ""));        copyDirRecursive(src, dest);        totalSkills++;      }    }    // Process extension references from the build-only extensions.json file.    const extensionRefs = readExtensionReferences(pluginPath, pluginName);    for (const extensionName of extensionRefs) {      const relPath = `./extensions/${extensionName}`;      const src = resolveSource(relPath);      if (!src) {        console.warn(`  ⚠ ${pluginName}: Unknown extension path format: ${relPath}`);        warnings++;        continue;      }      if (!fs.existsSync(src) || !fs.statSync(src).isDirectory()) {        console.warn(`  ⚠ ${pluginName}: Extension source directory not found: ${src}`);        warnings++;        continue;      }      const dest = path.join(pluginPath, relPath.replace(/^\.\//, "").replace(/\/$/, ""));      copyDirRecursive(src, dest);      totalExtensions++;    }    // Emit a spec-compliant served manifest for the marketplace branch.    // Source manifests keep composition fields (agents and skills)    // for build tooling. The served manifest retains only Agent Plugins v1.0.0 fields    // so the runtime uses conventional directory discovery for all content.    const SPEC_FIELDS = new Set(["$schema", "name", "version", "description", "author",      "homepage", "repository", "license", "keywords", "extensions"]);    const AGENT_PLUGINS_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";    const served = { "$schema": AGENT_PLUGINS_SCHEMA };    for (const [key, val] of Object.entries(metadata)) {      if (SPEC_FIELDS.has(key) && key !== "$schema") {        served[key] = val;      }    }    fs.writeFileSync(pluginJsonPath, JSON.stringify(served, null, 2) + "\n", "utf8");    const counts = [];    if (metadata.agents?.length) counts.push(`${metadata.agents.length} agents`);    if (metadata.skills?.length) counts.push(`${metadata.skills.length} skills`);    if (extensionRefs.length) counts.push(`${extensionRefs.length} extensions`);    if (counts.length) {      console.log(`✓ ${pluginName}: ${counts.join(", ")}`);    }  }  console.log(`\nDone. Copied ${totalAgents} agents, ${totalSkills} skills, ${totalExtensions} extensions.`);  if (warnings > 0) {    console.log(`${warnings} warning(s).`);  }  if (errors > 0) {    console.error(`${errors} error(s).`);    process.exit(1);  }}materializePlugins();

eng/validate-plugins.mjs:218

  • Exempting extension plugins from the README requirement leaves the newly migrated plugin directories with only plugin.json. The generated docs/README.plugins.md nevertheless links to plugins/<id>/README.md (for example accessibility-kanban and apng-studio), producing broken documentation links and violating the plugin layout documented in AGENTS.md:325-327. Keep the README requirement and add the corresponding plugin READMEs.
  if (!fs.existsSync(readmePath) && !isExtensionPlugin) {

.github/workflows/validate-plugins.yml:50

  • The failure guidance names extensions["<extension-id>"], but validation requires the literal com.github.copilot namespace. Following this message would still fail validation; show the exact required key.
              '- For **extensions**: `extensions["<extension-id>"].logo` set to `"assets/preview.png"`',

.github/plugin/marketplace.json:310

  • This adds a second connector-namespaces marketplace entry even though the external entry immediately above already publishes version 1.2.0, and plugins/connector-namespaces does not exist. Consumers may resolve the duplicate stale 1.1.0 entry to a nonexistent source; remove this generated duplicate.
    {
      "name": "connector-namespaces",
      "source": "plugins/connector-namespaces",
      "description": "Browse, connect, and open MCP connectors from an Azure Connector Namespace.",
      "version": "1.1.0"
    },
  • Files reviewed: 110/110 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread eng/materialize-plugins.mjs Outdated
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
materializePlugins();
}
#!/usr/bin/env nodeimport fs from "fs";import path from "path";import { ROOT_FOLDER } from "./constants.mjs";const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions");/** * Recursively copy a directory. */function copyDirRecursive(src, dest) { fs.mkdirSync(dest, { recursive: true }); for (const entry of fs.readdirSync(src, { withFileTypes: true })) { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { copyDirRecursive(srcPath, destPath); } else { fs.copyFileSync(srcPath, destPath); } }}/** * Resolve a plugin-relative path to the repo-root source file. * * ./agents/foo.md → ROOT/agents/foo.agent.md * ./skills/baz/ → ROOT/skills/baz/ */function resolveSource(relPath) { const basename = path.basename(relPath, ".md"); if (relPath.startsWith("./agents/")) { return path.join(ROOT_FOLDER, "agents", `${basename}.agent.md`); } if (relPath.startsWith("./skills/")) { // Strip trailing slash and get the skill folder name const skillName = relPath.replace(/^\.\/skills\//, "").replace(/\/$/, ""); return path.join(ROOT_FOLDER, "skills", skillName); } if (relPath.startsWith("./extensions/")) { const extensionName = relPath.replace(/^\.\/extensions\//, "").replace(/\/$/, ""); return path.join(ROOT_FOLDER, "extensions", extensionName); } return null;}function readExtensionReferences(pluginPath, pluginName) { const referencesPath = path.join(pluginPath, ".github/plugin/extensions.json"); let references = []; if (fs.existsSync(referencesPath)) { try { references = JSON.parse(fs.readFileSync(referencesPath, "utf8")); } catch (err) { throw new Error(`Failed to parse ${referencesPath}: ${err.message}`); } } if (!Array.isArray(references) || references.some((name) => typeof name !== "string")) { throw new Error(`${referencesPath} must contain an array of extension names`); } const names = new Set(references); if (fs.existsSync(path.join(EXTENSIONS_DIR, pluginName, "extension.mjs"))) { names.add(pluginName); } return [...names].sort();}function materializePlugins() { console.log("Materializing plugin files...\n"); if (!fs.existsSync(PLUGINS_DIR)) { console.error(`Error: Plugins directory not found at ${PLUGINS_DIR}`); process.exit(1); } const pluginDirs = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true }) .filter(entry => entry.isDirectory()) .map(entry => entry.name) .sort(); let totalAgents = 0; let totalSkills = 0; let totalExtensions = 0; let warnings = 0; let errors = 0; for (const dirName of pluginDirs) { const pluginPath = path.join(PLUGINS_DIR, dirName); const pluginJsonPath = path.join(pluginPath, ".github/plugin", "plugin.json"); if (!fs.existsSync(pluginJsonPath)) { continue; } let metadata; try { metadata = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8")); } catch (err) { console.error(`Error: Failed to parse ${pluginJsonPath}: ${err.message}`); errors++; continue; } const pluginName = metadata.name || dirName; // Process agents if (Array.isArray(metadata.agents)) { for (const relPath of metadata.agents) { const src = resolveSource(relPath); if (!src) { console.warn(` ⚠ ${pluginName}: Unknown path format: ${relPath}`); warnings++; continue; } if (!fs.existsSync(src)) { console.warn(` ⚠ ${pluginName}: Source not found: ${src}`); warnings++; continue; } const dest = path.join(pluginPath, relPath.replace(/^\.\//, "")); fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.copyFileSync(src, dest); totalAgents++; } } // Process skills if (Array.isArray(metadata.skills)) { for (const relPath of metadata.skills) { const src = resolveSource(relPath); if (!src) { console.warn(` ⚠ ${pluginName}: Unknown path format: ${relPath}`); warnings++; continue; } if (!fs.existsSync(src) || !fs.statSync(src).isDirectory()) { console.warn(` ⚠ ${pluginName}: Source directory not found: ${src}`); warnings++; continue; } const dest = path.join(pluginPath, relPath.replace(/^\.\//, "").replace(/\/$/, "")); copyDirRecursive(src, dest); totalSkills++; } } // Process extension references from the build-only extensions.json file. const extensionRefs = readExtensionReferences(pluginPath, pluginName); for (const extensionName of extensionRefs) { const relPath = `./extensions/${extensionName}`; const src = resolveSource(relPath); if (!src) { console.warn(` ⚠ ${pluginName}: Unknown extension path format: ${relPath}`); warnings++; continue; } if (!fs.existsSync(src) || !fs.statSync(src).isDirectory()) { console.warn(` ⚠ ${pluginName}: Extension source directory not found: ${src}`); warnings++; continue; } const dest = path.join(pluginPath, relPath.replace(/^\.\//, "").replace(/\/$/, "")); copyDirRecursive(src, dest); totalExtensions++; } // Emit a spec-compliant served manifest for the marketplace branch. // Source manifests keep composition fields (agents and skills) // for build tooling. The served manifest retains only Agent Plugins v1.0.0 fields // so the runtime uses conventional directory discovery for all content. const SPEC_FIELDS = new Set(["$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "extensions"]); const AGENT_PLUGINS_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"; const served = { "$schema": AGENT_PLUGINS_SCHEMA }; for (const [key, val] of Object.entries(metadata)) { if (SPEC_FIELDS.has(key) && key !== "$schema") { served[key] = val; } } fs.writeFileSync(pluginJsonPath, JSON.stringify(served, null, 2) + "\n", "utf8"); const counts = []; if (metadata.agents?.length) counts.push(`${metadata.agents.length} agents`); if (metadata.skills?.length) counts.push(`${metadata.skills.length} skills`); if (extensionRefs.length) counts.push(`${extensionRefs.length} extensions`); if (counts.length) { console.log(`✓ ${pluginName}: ${counts.join(", ")}`); } } console.log(`\nDone. Copied ${totalAgents} agents, ${totalSkills} skills, ${totalExtensions} extensions.`); if (warnings > 0) { console.log(`${warnings} warning(s).`); } if (errors > 0) { console.error(`${errors} error(s).`); process.exit(1); }}materializePlugins(); No newline at end of file
Comment thread eng/generate-marketplace.mjs Outdated

// Run the script
generateMarketplace();
#!/usr/bin/env nodeimport 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 MARKETPLACE_FILE = path.join(ROOT_FOLDER, ".github/plugin", "marketplace.json");/** * Read plugin metadata from plugin.json file * @param {string} pluginDir - Path to plugin directory * @returns {object|null} - Plugin metadata or null if not found */function readPluginMetadata(pluginDir) { const pluginJsonPath = path.join(pluginDir, ".github/plugin", "plugin.json"); if (!fs.existsSync(pluginJsonPath)) { console.warn(`Warning: No plugin.json found for ${path.basename(pluginDir)}`); return null; } try { const content = fs.readFileSync(pluginJsonPath, "utf8"); return JSON.parse(content); } catch (error) { console.error(`Error reading plugin.json for ${path.basename(pluginDir)}:`, error.message); return null; }}function collectLocalPluginsFromRoot(rootDir, sourcePrefix, includeEntry = () => true) { if (!fs.existsSync(rootDir)) { return []; } const entries = fs.readdirSync(rootDir, { withFileTypes: true }) .filter(entry => entry.isDirectory()) .filter(entry => includeEntry(entry.name)) .map(entry => entry.name) .sort(); const plugins = []; for (const dirName of entries) { const pluginPath = path.join(rootDir, dirName); const metadata = readPluginMetadata(pluginPath); if (!metadata) { continue; } plugins.push({ name: metadata.name, source: `${sourcePrefix}/${dirName}`, description: metadata.description, version: metadata.version || "1.0.0" }); } return plugins;}/** * Generate marketplace.json from plugin directories */function generateMarketplace() { console.log("Generating marketplace.json..."); if (!fs.existsSync(PLUGINS_DIR)) { console.error(`Error: Plugins directory (${PLUGINS_DIR}) was not found`); process.exit(1); } const plugins = collectLocalPluginsFromRoot(PLUGINS_DIR, "plugins"); console.log(`Found ${plugins.length} local plugin manifests`); // Read external plugins and merge as-is 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`); for (const ext of externalPlugins) { plugins.push(ext); console.log(`✓ Added external plugin: ${ext.name}`); } } // Sort all plugins by name (case-insensitive) plugins.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" })); // Create marketplace.json structure const marketplace = { name: "awesome-copilot", metadata: { description: "Community-driven collection of GitHub Copilot plugins, agents, prompts, and skills", version: "1.0.0" }, owner: { name: "GitHub", email: "copilot@github.com" }, plugins: plugins }; // Ensure directory exists const marketplaceDir = path.dirname(MARKETPLACE_FILE); if (!fs.existsSync(marketplaceDir)) { fs.mkdirSync(marketplaceDir, { recursive: true }); } // Write marketplace.json fs.writeFileSync(MARKETPLACE_FILE, JSON.stringify(marketplace, null, 2) + "\n"); console.log(`\n✓ Successfully generated marketplace.json with ${plugins.length} plugins (${plugins.length - externalPlugins.length} local, ${externalPlugins.length} external)`); console.log(` Location: ${MARKETPLACE_FILE}`);}// Run the scriptgenerateMarketplace(); No newline at end of file
Comment on lines +298 to +300
const sourceUrl = String(entry?.source_tree_url || '');
const locator = String(entry?.source?.sha || entry?.source?.ref || 'repository');
const sourceCell = sourceUrl ? `[${locator}](${sourceUrl})` : locator;
Use root plugin.json manifests and namespaced extension directories throughout local tooling, validation, generation, and contributor documentation. Restore materialize-plugins.mjs line breaks so the source remains readable in GitHub.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f3a88cb-e01e-4760-8125-460490dc1a76
Copilot AI review requested due to automatic review settings August 5, 2026 01:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (9)

eng/external-plugin-quality-gates.mjs:186

  • This function is declared inside the for block in findPluginJson, so it is block-scoped and unavailable to runExternalPluginQualityGates at line 990. Every quality-gate run that reaches the new spec check throws ReferenceError, is caught as an infrastructure error, and never runs the remaining gates. Move this declaration to module scope after closing findPluginJson.
    function inspectAgentPluginSpecCompliance(pluginRoot) {

.github/workflows/external-plugin-pr-quality-gates.yml:300

  • These values come from external submission metadata, and source.ref may legally contain Markdown table delimiters. Removing the existing escaping lets a ref such as v1|... corrupt or inject content into the bot's PR comment. Keep the GitHub URL allowlist and table-cell escaping that are already defined above.
                  const sourceUrl = String(entry?.source_tree_url || '');
                  const locator = String(entry?.source?.sha || entry?.source?.ref || 'repository');
                  const sourceCell = sourceUrl ? `[${locator}](${sourceUrl})` : locator;

.github/workflows/validate-plugins.yml:50

  • This failure guidance names the namespace as <extension-id>, but validation requires the literal com.github.copilot namespace. Users following the error message will produce another invalid manifest; show extensions["com.github.copilot"].logo instead.
              '- For **extensions**: `extensions["<extension-id>"].logo` set to `"assets/preview.png"`',

eng/external-plugin-quality-gates.mjs:25

  • The escaped dots are incorrect for a regex literal: \\.\\. matches backslash-containing text rather than two literal periods, so names such as a..b incorrectly pass this new compliance check. Use \.\. in the regex literal, matching the existing canonical validation in eng/external-plugin-validation.mjs:103-105.
const AGENT_PLUGIN_NAME_PATTERN = /^(?!.*(?:--|\\.\\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;

.github/skills/create-canvas-extension/SKILL.md:43

  • This scaffold still places plugin.json under the removed .github/plugin/ layout, contradicting the root path required later in this same skill and by the migrated tooling. Following the skill would create an invalid extension plugin.
plugins/<extension-id>/
├── .github/
│   └── plugin/
│       └── plugin.json
└── README.md

plugins/the-workshop/.github/plugin/extensions.json:3

  • This build-only mapping is never read: repository search finds no consumer for .github/plugin/extensions.json, while materialize-plugins.mjs:50-64 reads extension references from plugin.json. That contradicts the PR description's mapping-file design and means future mappings created according to that design will be ignored. Either wire this file into materialization/validation or remove it and document the manifest field as the source of truth.
    eng/update-readme.mjs:718
  • Switching this reader to root manifests now includes extension-only plugins, but the table generator at lines 789-792 still counts only agents, commands, and skills. Consequently every newly listed canvas plugin is reported as 0 items in docs/README.plugins.md despite shipping an extension. Count extension references (including a matching extension source) and regenerate the documentation.
  const jsonPath = path.join(pluginDir, "plugin.json");

.github/skills/create-canvas-extension/SKILL.md:86

  • The instruction says to update plugin.json but then describes its entire contents as a JSON array of IDs. An agent following this literally would overwrite the required manifest object with an invalid array. Show the extensions.com.github.copilot.directories property inside a manifest object, including the required ./extensions/ path prefix.
Its contents must be a sorted JSON array of extension IDs:

```json
[
  "<extension-id>"
]

docs/README.plugins.md:30

  • This and the other newly added canvas rows link to plugins/<id>/README.md, but these migrated plugin directories do not contain those files (for example, plugins/accessibility-kanban/README.md is absent). The generated catalog therefore introduces broken links. Either add the required plugin READMEs or make the generator link to an existing extension README/fallback before regenerating this table.
| [accessibility-kanban](../plugins/accessibility-kanban/README.md) | Kanban board to manage accessibility issues, allow you to plan, track, and complete remediation work. | 0 items | accessibility, github-issues, issue-triage, kanban-board, planning-workflow, status-tracking |
  • Files reviewed: 115/115 changed files
  • Comments generated: 1
  • Review effort level: Balanced

if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
materializePlugins();
}
materializePlugins();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-submission PR adds at least one new contribution plugin PR touches plugins workflow PR touches workflow automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants