chore: publish from main

This commit is contained in:
github-actions[bot]
2026-08-13 01:24:35 +00:00
parent 0fbdfba718
commit bd74f084d2
498 changed files with 78 additions and 55 deletions
+3 -3
View File
@@ -9,7 +9,7 @@ The Awesome GitHub Copilot repository is a community-driven collection of custom
- **Skills** - Self-contained folders with instructions and bundled resources for specialized tasks
- **Hooks** - Automated workflows triggered by specific events during development
- **Workflows** - [Agentic Workflows](https://github.github.com/gh-aw) for AI-powered repository automation in GitHub Actions
- **Plugins** - Installable packages that group related agents, commands, and skills around specific themes
- **Plugins** - Installable packages that group related agents, hooks, and skills around specific themes
## Repository Structure
@@ -120,7 +120,7 @@ All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must
- plugin.json must have `name` field (matching the folder name)
- plugin.json must have `description` field (describing the plugin's purpose)
- plugin.json must have `version` field (semantic version, e.g., "1.0.0")
- Plugin content is defined declaratively in plugin.json under `extensions.com.github.awesome-copilot` using source-only composition fields (`agents`, `commands`, `hooks`, `skills`, and `extensions`). Source files live in top-level directories and are materialized into plugins by CI. This namespace is stripped from the served manifest — conventional directory discovery handles the materialized content in spec mode.
- Plugin content is defined declaratively in plugin.json under `extensions.com.github.awesome-copilot` using source-only composition fields (`agents`, `hooks`, `skills`, and `extensions`). Source files live in top-level directories and are materialized into plugins by CI. This namespace is stripped from the served manifest — skills use the standard `skills/` directory and Copilot-specific content uses `com.github.copilot/`.
- The `marketplace.json` file is automatically generated from all plugins during build
- Plugins are discoverable and installable via GitHub Copilot CLI
@@ -165,7 +165,7 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin:
**For Plugins:**
1. Run `npm run plugin:create -- --name <plugin-name>` to scaffold a new plugin
2. Define agents, commands, hooks, skills, and reusable extensions under `extensions.com.github.awesome-copilot` in `plugin.json`
2. Define agents, hooks, skills, and reusable extensions under `extensions.com.github.awesome-copilot` in `plugin.json`
3. Edit the generated `plugin.json` with your metadata
4. Run `npm run plugin:validate` to validate the plugin structure
5. Run `npm run build` to update README.md and marketplace.json
+4 -5
View File
@@ -153,11 +153,11 @@ Canvas extensions live in `extensions/<extension-id>/` as reusable source compon
### Adding Plugins
Plugins group related agents, commands, and skills around specific themes or workflows, making it easy for users to install comprehensive toolkits via GitHub Copilot CLI.
Plugins group related agents, hooks, and skills around specific themes or workflows, making it easy for users to install comprehensive toolkits via GitHub Copilot CLI.
1. **Create your plugin**: Run `npm run plugin:create` to scaffold a new plugin
2. **Follow the naming convention**: Use descriptive, lowercase folder names with hyphens (e.g., `python-web-development`)
3. **Define your content**: List agents, commands, hooks, skills, and reusable extensions under `extensions.com.github.awesome-copilot` in `plugin.json`
3. **Define your content**: List agents, hooks, skills, and reusable extensions under `extensions.com.github.awesome-copilot` in `plugin.json`
4. **Test your plugin**: Run `npm run plugin:validate` to verify your plugin structure
#### Creating a plugin
@@ -174,7 +174,7 @@ plugins/my-plugin-id/
└── README.md # Plugin documentation
```
> **Note:** Plugin content is defined declaratively in plugin.json under `extensions.com.github.awesome-copilot`. Source files live in top-level directories and are materialized into plugins by CI. This repository namespace is removed from the served manifest.
> **Note:** Plugin content is defined declaratively in plugin.json under `extensions.com.github.awesome-copilot`. Source files live in top-level directories and are materialized into plugins by CI. Skills are emitted under `skills/`; Copilot-specific agents, hooks, and extensions are emitted under `com.github.copilot/`. This repository namespace is removed from the served manifest.
#### plugin.json example
@@ -191,7 +191,6 @@ plugins/my-plugin-id/
"extensions": {
"com.github.awesome-copilot": {
"agents": ["./agents/my-agent.md"],
"commands": ["./commands/my-command.md"],
"skills": ["./skills/my-skill/"]
}
}
@@ -200,7 +199,7 @@ plugins/my-plugin-id/
#### Plugin Guidelines
- **Declarative content**: Plugin content is specified under `extensions.com.github.awesome-copilot` — source files live in top-level directories and are materialized into plugins by CI
- **Declarative content**: Plugin content is specified under `extensions.com.github.awesome-copilot` — source files live in top-level directories and are materialized into plugins by CI. Skills use the standard `skills/` directory; Copilot-specific content uses `com.github.copilot/`.
- **Valid references**: All paths referenced in plugin.json must point to existing source files in the repository
- **Reusable extensions**: Curated plugins can bundle extensions by adding `./extensions/<name>` paths under `extensions.com.github.awesome-copilot.extensions`; the same extension can be listed by multiple plugins
- **Instructions excluded**: Instructions are standalone resources and are not part of plugins
+56 -28
View File
@@ -7,25 +7,35 @@ import { ROOT_FOLDER } from "./constants.mjs";
const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions");
const COPILOT_CONTENT_DIR = "com.github.copilot";
const AWESOME_COPILOT_NAMESPACE = "com.github.awesome-copilot";
const MATERIALIZED_SPECS = {
agents: {
path: "agents",
paths: [path.join(COPILOT_CONTENT_DIR, "agents"), "agents"],
restore(dirPath) {
return collectFiles(dirPath).map((relativePath) => `./agents/${relativePath}`);
},
},
commands: {
path: "commands",
hooks: {
paths: [path.join(COPILOT_CONTENT_DIR, "hooks"), "hooks"],
restore(dirPath) {
return collectFiles(dirPath).map((relativePath) => `./commands/${relativePath}`);
return collectDirectoriesContainingFile(dirPath, "hooks.json")
.map((relativePath) => `./hooks/${relativePath}/`);
},
},
skills: {
path: "skills",
paths: ["skills"],
restore(dirPath) {
return collectSkillDirectories(dirPath).map((relativePath) => `./skills/${relativePath}/`);
},
},
extensions: {
paths: [path.join(COPILOT_CONTENT_DIR, "extensions"), "extensions"],
restore(dirPath) {
return collectDirectoriesContainingFile(dirPath, "extension.mjs")
.map((relativePath) => `./extensions/${relativePath}`);
},
},
};
function copyDirRecursive(src, dest) {
@@ -78,22 +88,19 @@ export function restoreManifestFromMaterializedFiles(pluginPath) {
let changed = false;
for (const [field, spec] of Object.entries(MATERIALIZED_SPECS)) {
if (Array.isArray(plugin[field])) {
const sortedEntries = sortPluginEntries(plugin[field]);
if (!arraysEqual(plugin[field], sortedEntries)) {
plugin[field] = sortedEntries;
changed = true;
}
}
const materializedPath = path.join(pluginPath, spec.path);
if (!fs.existsSync(materializedPath) || !fs.statSync(materializedPath).isDirectory()) {
const materializedPath = spec.paths
.map((subdir) => path.join(pluginPath, subdir))
.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isDirectory());
if (!materializedPath) {
continue;
}
const restored = spec.restore(materializedPath);
if (!arraysEqual(plugin[field], restored)) {
plugin[field] = restored;
const composition = plugin.extensions?.[AWESOME_COPILOT_NAMESPACE];
if (!arraysEqual(composition?.[field], restored)) {
plugin.extensions ??= {};
plugin.extensions[AWESOME_COPILOT_NAMESPACE] ??= {};
plugin.extensions[AWESOME_COPILOT_NAMESPACE][field] = restored;
changed = true;
}
}
@@ -112,16 +119,23 @@ function cleanPlugin(pluginPath) {
}
let removed = 0;
for (const { path: subdir } of Object.values(MATERIALIZED_SPECS)) {
const target = path.join(pluginPath, subdir);
if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
const count = countFiles(target);
fs.rmSync(target, { recursive: true, force: true });
removed += count;
console.log(` Removed ${path.basename(pluginPath)}/${subdir}/ (${count} files)`);
for (const { paths } of Object.values(MATERIALIZED_SPECS)) {
for (const subdir of paths) {
const target = path.join(pluginPath, subdir);
if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
const count = countFiles(target);
fs.rmSync(target, { recursive: true, force: true });
removed += count;
console.log(` Removed ${path.basename(pluginPath)}/${subdir}/ (${count} files)`);
}
}
}
const copilotContentPath = path.join(pluginPath, COPILOT_CONTENT_DIR);
if (fs.existsSync(copilotContentPath) && fs.readdirSync(copilotContentPath).length === 0) {
fs.rmdirSync(copilotContentPath);
}
return { removed, manifestUpdated };
}
@@ -230,6 +244,24 @@ function collectSkillDirectories(dir, rootDir = dir) {
return skillDirs.sort();
}
function collectDirectoriesContainingFile(dir, fileName, rootDir = dir) {
const directories = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (!entry.isDirectory()) {
continue;
}
const entryPath = path.join(dir, entry.name);
if (fs.existsSync(path.join(entryPath, fileName))) {
directories.push(toPosixPath(path.relative(rootDir, entryPath)));
continue;
}
directories.push(...collectDirectoriesContainingFile(entryPath, fileName, rootDir));
}
return directories.sort();
}
function arraysEqual(left, right) {
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
return false;
@@ -238,10 +270,6 @@ function arraysEqual(left, right) {
return left.every((value, index) => value === right[index]);
}
function sortPluginEntries(entries) {
return [...entries].sort((left, right) => left.localeCompare(right));
}
function toPosixPath(filePath) {
return filePath.split(path.sep).join("/");
}
+1 -2
View File
@@ -618,10 +618,9 @@ function generatePluginsData(gitDates, resourceIndex = {}) {
}
}
// Build items list from spec fields (agents, commands, skills, mcpServers)
// Build items list from supported composition fields.
const items = [
...agentItems,
...(composition.commands || []).map((p) => ({ kind: "prompt", path: p })),
...(composition.skills || []).map((p) => ({ kind: "skill", path: p })),
...extensionItems,
...mcpItems,
+11 -11
View File
@@ -9,6 +9,7 @@ const PLUGINS_DIR = path.join(ROOT_FOLDER, "plugins");
const EXTENSIONS_DIR = path.join(ROOT_FOLDER, "extensions");
const COPILOT_NAMESPACE = "com.github.copilot";
const AWESOME_COPILOT_NAMESPACE = "com.github.awesome-copilot";
const COPILOT_CONTENT_DIR = COPILOT_NAMESPACE;
/**
* Recursively copy a directory.
@@ -49,9 +50,6 @@ function resolveSource(relPath) {
if (relPath.startsWith("./hooks/")) {
return path.join(ROOT_FOLDER, "hooks", relPath.replace(/^\.\/hooks\//, ""));
}
if (relPath.startsWith("./commands/")) {
return path.join(ROOT_FOLDER, "commands", relPath.replace(/^\.\/commands\//, ""));
}
return null;
}
@@ -114,7 +112,7 @@ export function materializePlugins() {
const composition = metadata.extensions?.[AWESOME_COPILOT_NAMESPACE] ?? {};
// Process repository composition fields.
for (const field of ["agents", "commands", "hooks", "skills"]) {
for (const field of ["agents", "hooks", "skills"]) {
const entries = composition[field];
if (!Array.isArray(entries)) continue;
for (const relPath of entries) {
@@ -129,7 +127,10 @@ export function materializePlugins() {
warnings++;
continue;
}
const dest = path.join(pluginPath, relPath.replace(/^\.\//, "").replace(/\/$/, ""));
const relativeDestination = relPath.replace(/^\.\//, "").replace(/\/$/, "");
const dest = field === "skills"
? path.join(pluginPath, relativeDestination)
: path.join(pluginPath, COPILOT_CONTENT_DIR, relativeDestination);
fs.mkdirSync(path.dirname(dest), { recursive: true });
if (fs.statSync(src).isDirectory()) copyDirRecursive(src, dest);
else fs.copyFileSync(src, dest);
@@ -153,17 +154,16 @@ export function materializePlugins() {
warnings++;
continue;
}
// Extensions are conventional plugin content and belong under the
// plugin's top-level extensions directory, not the client namespace.
const dest = path.join(pluginPath, "extensions", extensionName);
const dest = path.join(pluginPath, COPILOT_CONTENT_DIR, "extensions", extensionName);
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.
// Source manifests keep repository composition fields for build tooling.
// The served manifest retains only Agent Plugins v1.0.0 fields; standard
// skills are discovered from skills/, while Copilot-specific content is
// discovered from com.github.copilot/.
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";
-2
View File
@@ -800,7 +800,6 @@ function generatePluginsSection(pluginsDir) {
: 0;
const itemCount =
(composition.agents || []).length +
(composition.commands || []).length +
(composition.skills || []).length +
extensionReferences +
implicitExtension;
@@ -869,7 +868,6 @@ function generateFeaturedPluginsSection(pluginsDir) {
: 0;
const itemCount =
(composition.agents || []).length +
(composition.commands || []).length +
(composition.skills || []).length +
extensionReferences +
implicitExtension;
+3 -4
View File
@@ -120,7 +120,6 @@ function validateSpecPaths(plugin) {
const errors = [];
const specs = {
agents: { prefix: "./agents/", suffix: ".md", repoDir: "agents", repoSuffix: ".agent.md" },
commands: { prefix: "./commands/", suffix: ".md", repoDir: "commands", repoSuffix: ".md" },
hooks: { prefix: "./hooks/", suffix: "/", repoDir: "hooks", repoFile: "README.md" },
skills: { prefix: "./skills/", suffix: "/", repoDir: "skills", repoFile: "SKILL.md" },
};
@@ -215,7 +214,7 @@ function validateExtensionReferences(plugin, pluginDir) {
function validateCompositionNamespace(plugin) {
const errors = [];
const compositionFields = ["agents", "commands", "hooks", "mcpServers", "skills"];
const compositionFields = ["agents", "hooks", "mcpServers", "skills"];
const extensions = plugin.extensions;
const composition = extensions?.[AWESOME_COPILOT_NAMESPACE];
@@ -291,7 +290,7 @@ function validatePlugin(folderName) {
// Rule 5b: license (shared with external plugins). Non-SPDX is a warning, not an error.
const warnings = [];
for (const field of ["agents", "commands", "hooks", "mcpServers", "skills"]) {
for (const field of ["agents", "hooks", "mcpServers", "skills"]) {
if (plugin[field] !== undefined) {
errors.push(`${field} must be moved to extensions["${AWESOME_COPILOT_NAMESPACE}"].${field}`);
}
@@ -301,7 +300,7 @@ function validatePlugin(folderName) {
errors.push(...licenseResult.errors);
warnings.push(...licenseResult.warnings);
// Rule 6: agents, commands, skills paths
// Rule 6: agents, hooks, and skills paths
const specErrors = validateSpecPaths(plugin);
errors.push(...specErrors);

Before

Width:  |  Height:  |  Size: 380 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Before

Width:  |  Height:  |  Size: 296 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 518 B

Some files were not shown because too many files have changed in this diff Show More