mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-30 04:05:55 +00:00
Consolidate scripts and automate report management (#1540)
* removing old scripts * consolidated folder * Updating usage of scripts * Adding script to generate an open PR report, rather than making AI gen it each time * Adding step to close old quality report discussions
This commit is contained in:
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Fix line endings
|
- name: Fix line endings
|
||||||
run: bash scripts/fix-line-endings.sh
|
run: bash eng/fix-line-endings.sh
|
||||||
|
|
||||||
- name: Publish to main
|
- name: Publish to main
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
78
.github/workflows/skill-quality-report.yml
vendored
78
.github/workflows/skill-quality-report.yml
vendored
@@ -297,6 +297,84 @@ jobs:
|
|||||||
}
|
}
|
||||||
core.setOutput('comment_count', String(commentParts.length));
|
core.setOutput('comment_count', String(commentParts.length));
|
||||||
|
|
||||||
|
- name: Close old report discussions
|
||||||
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const RETENTION_DAYS = 5;
|
||||||
|
const CATEGORY_NAME = 'Skill Quality Reports';
|
||||||
|
const TITLE_PREFIX = 'Skill Quality Report — ';
|
||||||
|
|
||||||
|
const cutoff = new Date();
|
||||||
|
cutoff.setUTCDate(cutoff.getUTCDate() - RETENTION_DAYS);
|
||||||
|
|
||||||
|
let after = null;
|
||||||
|
let closedCount = 0;
|
||||||
|
let reachedCutoff = false;
|
||||||
|
|
||||||
|
while (!reachedCutoff) {
|
||||||
|
const result = await github.graphql(`
|
||||||
|
query($owner: String!, $repo: String!, $after: String) {
|
||||||
|
repository(owner: $owner, name: $repo) {
|
||||||
|
discussions(first: 100, after: $after, orderBy: { field: CREATED_AT, direction: ASC }) {
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
createdAt
|
||||||
|
closed
|
||||||
|
url
|
||||||
|
category {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
after,
|
||||||
|
});
|
||||||
|
|
||||||
|
const discussions = result.repository.discussions;
|
||||||
|
|
||||||
|
for (const discussion of discussions.nodes) {
|
||||||
|
if (new Date(discussion.createdAt) >= cutoff) {
|
||||||
|
reachedCutoff = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discussion.category?.name !== CATEGORY_NAME) continue;
|
||||||
|
if (!discussion.title.startsWith(TITLE_PREFIX)) continue;
|
||||||
|
if (discussion.closed) continue;
|
||||||
|
|
||||||
|
await github.graphql(`
|
||||||
|
mutation($discussionId: ID!) {
|
||||||
|
closeDiscussion(input: { discussionId: $discussionId }) {
|
||||||
|
discussion {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, { discussionId: discussion.id });
|
||||||
|
|
||||||
|
closedCount++;
|
||||||
|
console.log(`Closed old report discussion: ${discussion.url}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reachedCutoff || !discussions.pageInfo.hasNextPage) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
after = discussions.pageInfo.endCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Closed ${closedCount} report discussion(s) older than ${RETENTION_DAYS} days.`);
|
||||||
|
|
||||||
# ── Create Discussion (preferred) or Issue (fallback) ────────
|
# ── Create Discussion (preferred) or Issue (fallback) ────────
|
||||||
- name: Create Discussion
|
- name: Create Discussion
|
||||||
id: create-discussion
|
id: create-discussion
|
||||||
|
|||||||
57
AGENTS.md
57
AGENTS.md
@@ -57,18 +57,21 @@ npm run skill:create -- --name <skill-name>
|
|||||||
|
|
||||||
All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must include proper markdown front matter. Agent Skills are folders containing a `SKILL.md` file with frontmatter and optional bundled assets. Hooks are folders containing a `README.md` with frontmatter and a `hooks.json` configuration file:
|
All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must include proper markdown front matter. Agent Skills are folders containing a `SKILL.md` file with frontmatter and optional bundled assets. Hooks are folders containing a `README.md` with frontmatter and a `hooks.json` configuration file:
|
||||||
|
|
||||||
#### Agent Files (*.agent.md)
|
#### Agent Files (\*.agent.md)
|
||||||
|
|
||||||
- Must have `description` field (wrapped in single quotes)
|
- Must have `description` field (wrapped in single quotes)
|
||||||
- File names should be lower case with words separated by hyphens
|
- File names should be lower case with words separated by hyphens
|
||||||
- Recommended to include `tools` field
|
- Recommended to include `tools` field
|
||||||
- Strongly recommended to specify `model` field
|
- Strongly recommended to specify `model` field
|
||||||
|
|
||||||
#### Instruction Files (*.instructions.md)
|
#### Instruction Files (\*.instructions.md)
|
||||||
|
|
||||||
- Must have `description` field (wrapped in single quotes, not empty)
|
- Must have `description` field (wrapped in single quotes, not empty)
|
||||||
- Must have `applyTo` field specifying file patterns (e.g., `'**.js, **.ts'`)
|
- Must have `applyTo` field specifying file patterns (e.g., `'**.js, **.ts'`)
|
||||||
- File names should be lower case with words separated by hyphens
|
- File names should be lower case with words separated by hyphens
|
||||||
|
|
||||||
#### Agent Skills (skills/*/SKILL.md)
|
#### Agent Skills (skills/\*/SKILL.md)
|
||||||
|
|
||||||
- Each skill is a folder containing a `SKILL.md` file
|
- Each skill is a folder containing a `SKILL.md` file
|
||||||
- SKILL.md must have `name` field (lowercase with hyphens, matching folder name, max 64 characters)
|
- SKILL.md must have `name` field (lowercase with hyphens, matching folder name, max 64 characters)
|
||||||
- SKILL.md must have `description` field (wrapped in single quotes, 10-1024 characters)
|
- SKILL.md must have `description` field (wrapped in single quotes, 10-1024 characters)
|
||||||
@@ -78,7 +81,8 @@ All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must
|
|||||||
- Asset files should be reasonably sized (under 5MB per file)
|
- Asset files should be reasonably sized (under 5MB per file)
|
||||||
- Skills follow the [Agent Skills specification](https://agentskills.io/specification)
|
- Skills follow the [Agent Skills specification](https://agentskills.io/specification)
|
||||||
|
|
||||||
#### Hook Folders (hooks/*/README.md)
|
#### Hook Folders (hooks/\*/README.md)
|
||||||
|
|
||||||
- Each hook is a folder containing a `README.md` file with frontmatter
|
- Each hook is a folder containing a `README.md` file with frontmatter
|
||||||
- README.md must have `name` field (human-readable name)
|
- README.md must have `name` field (human-readable name)
|
||||||
- README.md must have `description` field (wrapped in single quotes, not empty)
|
- README.md must have `description` field (wrapped in single quotes, not empty)
|
||||||
@@ -89,7 +93,8 @@ All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must
|
|||||||
- Follow the [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks)
|
- Follow the [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks)
|
||||||
- Optionally includes `tags` field for categorization
|
- Optionally includes `tags` field for categorization
|
||||||
|
|
||||||
#### Workflow Files (workflows/*.md)
|
#### Workflow Files (workflows/\*.md)
|
||||||
|
|
||||||
- Each workflow is a standalone `.md` file in the `workflows/` directory
|
- Each workflow is a standalone `.md` file in the `workflows/` directory
|
||||||
- Must have `name` field (human-readable name)
|
- Must have `name` field (human-readable name)
|
||||||
- Must have `description` field (wrapped in single quotes, not empty)
|
- Must have `description` field (wrapped in single quotes, not empty)
|
||||||
@@ -98,7 +103,8 @@ All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must
|
|||||||
- Only `.md` files are accepted — `.yml`, `.yaml`, and `.lock.yml` files are blocked by CI
|
- Only `.md` files are accepted — `.yml`, `.yaml`, and `.lock.yml` files are blocked by CI
|
||||||
- Follow the [GitHub Agentic Workflows specification](https://github.github.com/gh-aw/reference/workflow-structure/)
|
- Follow the [GitHub Agentic Workflows specification](https://github.github.com/gh-aw/reference/workflow-structure/)
|
||||||
|
|
||||||
#### Plugin Folders (plugins/*)
|
#### Plugin Folders (plugins/\*)
|
||||||
|
|
||||||
- Each plugin is a folder containing a `.github/plugin/plugin.json` file with metadata
|
- Each plugin is a folder containing a `.github/plugin/plugin.json` file with metadata
|
||||||
- plugin.json must have `name` field (matching the folder name)
|
- 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 `description` field (describing the plugin's purpose)
|
||||||
@@ -112,12 +118,14 @@ All agent files (`*.agent.md`) and instruction files (`*.instructions.md`) must
|
|||||||
When adding a new agent, instruction, skill, hook, workflow, or plugin:
|
When adding a new agent, instruction, skill, hook, workflow, or plugin:
|
||||||
|
|
||||||
**For Agents and Instructions:**
|
**For Agents and Instructions:**
|
||||||
|
|
||||||
1. Create the file with proper front matter
|
1. Create the file with proper front matter
|
||||||
2. Add the file to the appropriate directory
|
2. Add the file to the appropriate directory
|
||||||
3. Update the README.md by running: `npm run build`
|
3. Update the README.md by running: `npm run build`
|
||||||
4. Verify the resource appears in the generated README
|
4. Verify the resource appears in the generated README
|
||||||
|
|
||||||
**For Hooks:**
|
**For Hooks:**
|
||||||
|
|
||||||
1. Create a new folder in `hooks/` with a descriptive name
|
1. Create a new folder in `hooks/` with a descriptive name
|
||||||
2. Create `README.md` with proper frontmatter (name, description, hooks, tags)
|
2. Create `README.md` with proper frontmatter (name, description, hooks, tags)
|
||||||
3. Create `hooks.json` with hook configuration following GitHub Copilot hooks spec
|
3. Create `hooks.json` with hook configuration following GitHub Copilot hooks spec
|
||||||
@@ -126,16 +134,16 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin:
|
|||||||
6. Update the README.md by running: `npm run build`
|
6. Update the README.md by running: `npm run build`
|
||||||
7. Verify the hook appears in the generated README
|
7. Verify the hook appears in the generated README
|
||||||
|
|
||||||
|
|
||||||
**For Workflows:**
|
**For Workflows:**
|
||||||
|
|
||||||
1. Create a new `.md` file in `workflows/` with a descriptive name (e.g., `daily-issues-report.md`)
|
1. Create a new `.md` file in `workflows/` with a descriptive name (e.g., `daily-issues-report.md`)
|
||||||
2. Include frontmatter with `name` and `description`, plus agentic workflow fields (`on`, `permissions`, `safe-outputs`)
|
2. Include frontmatter with `name` and `description`, plus agentic workflow fields (`on`, `permissions`, `safe-outputs`)
|
||||||
3. Compile with `gh aw compile --validate` to verify it's valid
|
3. Compile with `gh aw compile --validate` to verify it's valid
|
||||||
4. Update the README.md by running: `npm run build`
|
4. Update the README.md by running: `npm run build`
|
||||||
5. Verify the workflow appears in the generated README
|
5. Verify the workflow appears in the generated README
|
||||||
|
|
||||||
|
|
||||||
**For Skills:**
|
**For Skills:**
|
||||||
|
|
||||||
1. Run `npm run skill:create` to scaffold a new skill folder
|
1. Run `npm run skill:create` to scaffold a new skill folder
|
||||||
2. Edit the generated SKILL.md file with your instructions
|
2. Edit the generated SKILL.md file with your instructions
|
||||||
3. Add any bundled assets (scripts, templates, data) to the skill folder
|
3. Add any bundled assets (scripts, templates, data) to the skill folder
|
||||||
@@ -144,6 +152,7 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin:
|
|||||||
6. Verify the skill appears in the generated README
|
6. Verify the skill appears in the generated README
|
||||||
|
|
||||||
**For Plugins:**
|
**For Plugins:**
|
||||||
|
|
||||||
1. Run `npm run plugin:create -- --name <plugin-name>` to scaffold a new plugin
|
1. Run `npm run plugin:create -- --name <plugin-name>` to scaffold a new plugin
|
||||||
2. Define agents, commands, and skills in `plugin.json` using Claude Code spec fields
|
2. Define agents, commands, and skills in `plugin.json` using Claude Code spec fields
|
||||||
3. Edit the generated `plugin.json` with your metadata
|
3. Edit the generated `plugin.json` with your metadata
|
||||||
@@ -152,6 +161,7 @@ When adding a new agent, instruction, skill, hook, workflow, or plugin:
|
|||||||
6. Verify the plugin appears in `.github/plugin/marketplace.json`
|
6. Verify the plugin appears in `.github/plugin/marketplace.json`
|
||||||
|
|
||||||
**For External Plugins:**
|
**For External Plugins:**
|
||||||
|
|
||||||
1. Edit `plugins/external.json` and add an entry with `name`, `source`, `description`, and `version`
|
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))
|
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
|
3. Run `npm run build` to regenerate marketplace.json
|
||||||
@@ -168,25 +178,28 @@ npm run skill:validate
|
|||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Fix line endings (required before committing)
|
# Fix line endings (required before committing)
|
||||||
bash scripts/fix-line-endings.sh
|
bash eng/fix-line-endings.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Before committing:
|
Before committing:
|
||||||
|
|
||||||
- Ensure all markdown front matter is correctly formatted
|
- Ensure all markdown front matter is correctly formatted
|
||||||
- Verify file names follow the lower-case-with-hyphens convention
|
- Verify file names follow the lower-case-with-hyphens convention
|
||||||
- Run `npm run build` to update the README
|
- Run `npm run build` to update the README
|
||||||
- **Always run `bash scripts/fix-line-endings.sh`** to normalize line endings (CRLF → LF)
|
- **Always run `bash eng/fix-line-endings.sh`** to normalize line endings (CRLF → LF)
|
||||||
- Check that your new resource appears correctly in the README
|
- Check that your new resource appears correctly in the README
|
||||||
|
|
||||||
## Code Style Guidelines
|
## Code Style Guidelines
|
||||||
|
|
||||||
### Markdown Files
|
### Markdown Files
|
||||||
|
|
||||||
- Use proper front matter with required fields
|
- Use proper front matter with required fields
|
||||||
- Keep descriptions concise and informative
|
- Keep descriptions concise and informative
|
||||||
- Wrap description field values in single quotes
|
- Wrap description field values in single quotes
|
||||||
- Use lower-case file names with hyphens as separators
|
- Use lower-case file names with hyphens as separators
|
||||||
|
|
||||||
### JavaScript/Node.js Scripts
|
### JavaScript/Node.js Scripts
|
||||||
|
|
||||||
- Located in `eng/` and `scripts/` directories
|
- Located in `eng/` and `scripts/` directories
|
||||||
- Follow Node.js ES module conventions (`.mjs` extension)
|
- Follow Node.js ES module conventions (`.mjs` extension)
|
||||||
- Use clear, descriptive function and variable names
|
- Use clear, descriptive function and variable names
|
||||||
@@ -201,29 +214,32 @@ When creating a pull request:
|
|||||||
2. **Front matter validation**: Ensure all markdown files have the required front matter fields
|
2. **Front matter validation**: Ensure all markdown files have the required front matter fields
|
||||||
3. **File naming**: Verify all new files follow the lower-case-with-hyphens naming convention
|
3. **File naming**: Verify all new files follow the lower-case-with-hyphens naming convention
|
||||||
4. **Build check**: Run `npm run build` before committing to verify README generation
|
4. **Build check**: Run `npm run build` before committing to verify README generation
|
||||||
5. **Line endings**: **Always run `bash scripts/fix-line-endings.sh`** to normalize line endings to LF (Unix-style)
|
5. **Line endings**: **Always run `bash eng/fix-line-endings.sh`** to normalize line endings to LF (Unix-style)
|
||||||
6. **Description**: Provide a clear description of what your agent/instruction does
|
6. **Description**: Provide a clear description of what your agent/instruction does
|
||||||
7. **Testing**: If adding a plugin, run `npm run plugin:validate` to ensure validity
|
7. **Testing**: If adding a plugin, run `npm run plugin:validate` to ensure validity
|
||||||
|
|
||||||
### Pre-commit Checklist
|
### Pre-commit Checklist
|
||||||
|
|
||||||
Before submitting your PR, ensure you have:
|
Before submitting your PR, ensure you have:
|
||||||
|
|
||||||
- [ ] Run `npm install` (or `npm ci`) to install dependencies
|
- [ ] Run `npm install` (or `npm ci`) to install dependencies
|
||||||
- [ ] Run `npm run build` to generate the updated README.md
|
- [ ] Run `npm run build` to generate the updated README.md
|
||||||
- [ ] Run `bash scripts/fix-line-endings.sh` to normalize line endings
|
- [ ] Run `bash eng/fix-line-endings.sh` to normalize line endings
|
||||||
- [ ] Verified that all new files have proper front matter
|
- [ ] Verified that all new files have proper front matter
|
||||||
- [ ] Tested that your contribution works with GitHub Copilot
|
- [ ] Tested that your contribution works with GitHub Copilot
|
||||||
- [ ] Checked that file names follow the naming convention
|
- [ ] Checked that file names follow the naming convention
|
||||||
|
|
||||||
### Code Review Checklist
|
### Code Review Checklist
|
||||||
|
|
||||||
For instruction files (*.instructions.md):
|
For instruction files (\*.instructions.md):
|
||||||
|
|
||||||
- [ ] Has markdown front matter
|
- [ ] Has markdown front matter
|
||||||
- [ ] Has non-empty `description` field wrapped in single quotes
|
- [ ] Has non-empty `description` field wrapped in single quotes
|
||||||
- [ ] Has `applyTo` field with file patterns
|
- [ ] Has `applyTo` field with file patterns
|
||||||
- [ ] File name is lower case with hyphens
|
- [ ] File name is lower case with hyphens
|
||||||
|
|
||||||
For agent files (*.agent.md):
|
For agent files (\*.agent.md):
|
||||||
|
|
||||||
- [ ] Has markdown front matter
|
- [ ] Has markdown front matter
|
||||||
- [ ] Has non-empty `description` field wrapped in single quotes
|
- [ ] Has non-empty `description` field wrapped in single quotes
|
||||||
- [ ] Has `name` field with human-readable name (e.g., "Address Comments" not "address-comments")
|
- [ ] Has `name` field with human-readable name (e.g., "Address Comments" not "address-comments")
|
||||||
@@ -231,7 +247,8 @@ For agent files (*.agent.md):
|
|||||||
- [ ] Includes `model` field (strongly recommended)
|
- [ ] Includes `model` field (strongly recommended)
|
||||||
- [ ] Considers using `tools` field
|
- [ ] Considers using `tools` field
|
||||||
|
|
||||||
For skills (skills/*/):
|
For skills (skills/\*/):
|
||||||
|
|
||||||
- [ ] Folder contains a SKILL.md file
|
- [ ] Folder contains a SKILL.md file
|
||||||
- [ ] SKILL.md has markdown front matter
|
- [ ] SKILL.md has markdown front matter
|
||||||
- [ ] Has `name` field matching folder name (lowercase with hyphens, max 64 characters)
|
- [ ] Has `name` field matching folder name (lowercase with hyphens, max 64 characters)
|
||||||
@@ -240,7 +257,8 @@ For skills (skills/*/):
|
|||||||
- [ ] Any bundled assets are referenced in SKILL.md
|
- [ ] Any bundled assets are referenced in SKILL.md
|
||||||
- [ ] Bundled assets are under 5MB per file
|
- [ ] Bundled assets are under 5MB per file
|
||||||
|
|
||||||
For hook folders (hooks/*/):
|
For hook folders (hooks/\*/):
|
||||||
|
|
||||||
- [ ] Folder contains a README.md file with markdown front matter
|
- [ ] Folder contains a README.md file with markdown front matter
|
||||||
- [ ] Has `name` field with human-readable name
|
- [ ] Has `name` field with human-readable name
|
||||||
- [ ] Has non-empty `description` field wrapped in single quotes
|
- [ ] Has non-empty `description` field wrapped in single quotes
|
||||||
@@ -250,7 +268,8 @@ For hook folders (hooks/*/):
|
|||||||
- [ ] Follows [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks)
|
- [ ] Follows [GitHub Copilot hooks specification](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks)
|
||||||
- [ ] Optionally includes `tags` array field for categorization
|
- [ ] Optionally includes `tags` array field for categorization
|
||||||
|
|
||||||
For workflow files (workflows/*.md):
|
For workflow files (workflows/\*.md):
|
||||||
|
|
||||||
- [ ] File has markdown front matter
|
- [ ] File has markdown front matter
|
||||||
- [ ] Has `name` field with human-readable name
|
- [ ] Has `name` field with human-readable name
|
||||||
- [ ] Has non-empty `description` field wrapped in single quotes
|
- [ ] Has non-empty `description` field wrapped in single quotes
|
||||||
@@ -260,7 +279,8 @@ For workflow files (workflows/*.md):
|
|||||||
- [ ] No `.yml`, `.yaml`, or `.lock.yml` files included
|
- [ ] No `.yml`, `.yaml`, or `.lock.yml` files included
|
||||||
- [ ] Follows [GitHub Agentic Workflows specification](https://github.github.com/gh-aw/reference/workflow-structure/)
|
- [ ] Follows [GitHub Agentic Workflows specification](https://github.github.com/gh-aw/reference/workflow-structure/)
|
||||||
|
|
||||||
For plugins (plugins/*/):
|
For plugins (plugins/\*/):
|
||||||
|
|
||||||
- [ ] Directory contains a `.github/plugin/plugin.json` file
|
- [ ] Directory contains a `.github/plugin/plugin.json` file
|
||||||
- [ ] Directory contains a `README.md` file
|
- [ ] Directory contains a `README.md` file
|
||||||
- [ ] `plugin.json` has `name` field matching the directory name (lowercase with hyphens)
|
- [ ] `plugin.json` has `name` field matching the directory name (lowercase with hyphens)
|
||||||
@@ -275,6 +295,7 @@ For plugins (plugins/*/):
|
|||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
This is a community-driven project. Contributions are welcome! Please see:
|
This is a community-driven project. Contributions are welcome! Please see:
|
||||||
|
|
||||||
- [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines
|
- [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines
|
||||||
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community standards
|
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community standards
|
||||||
- [SECURITY.md](SECURITY.md) for security policies
|
- [SECURITY.md](SECURITY.md) for security policies
|
||||||
|
|||||||
291
eng/generate-open-pr-report.mjs
Executable file
291
eng/generate-open-pr-report.mjs
Executable file
@@ -0,0 +1,291 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { ROOT_FOLDER } from "./constants.mjs";
|
||||||
|
import { setupGracefulShutdown } from "./utils/graceful-shutdown.mjs";
|
||||||
|
|
||||||
|
const DEFAULT_REPO = "github/awesome-copilot";
|
||||||
|
const DEFAULT_LIMIT = 500;
|
||||||
|
const DEFAULT_CMD_TIMEOUT = 30_000;
|
||||||
|
|
||||||
|
const REPORT_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
heading: "PRs that target `main`",
|
||||||
|
fileName: "prs-targeting-main.json",
|
||||||
|
predicate: (pr) => pr.targetBranch === "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "PRs that target `staged` which are passing all checks and have less than 10 files",
|
||||||
|
fileName: "prs-staged-passing-under-10-files.json",
|
||||||
|
predicate: (pr) => pr.targetBranch === "staged" && pr.checksPass && pr.fileCount < 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "PRs that target `staged` which have between 10 and 50 files",
|
||||||
|
fileName: "prs-staged-10-to-50-files.json",
|
||||||
|
predicate: (pr) => pr.targetBranch === "staged" && pr.fileCount >= 10 && pr.fileCount <= 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "PRs that target `staged` with greater than 50 files",
|
||||||
|
fileName: "prs-staged-over-50-files.json",
|
||||||
|
predicate: (pr) => pr.targetBranch === "staged" && pr.fileCount > 50
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
setupGracefulShutdown("generate-open-pr-report");
|
||||||
|
|
||||||
|
function printUsage() {
|
||||||
|
console.log(`Usage: node eng/generate-open-pr-report.mjs [--repo owner/name] [--output-dir path] [--limit N]
|
||||||
|
|
||||||
|
Generate open PR reports for a GitHub repository.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- open-pr-report.md
|
||||||
|
- prs-targeting-main.json
|
||||||
|
- prs-staged-passing-under-10-files.json
|
||||||
|
- prs-staged-10-to-50-files.json
|
||||||
|
- prs-staged-over-50-files.json
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--repo GitHub repository in owner/name format (default: ${DEFAULT_REPO})
|
||||||
|
--output-dir Directory for generated reports (default: <repo-root>/reports)
|
||||||
|
--limit Max number of open PRs to fetch (default: ${DEFAULT_LIMIT})
|
||||||
|
--help, -h Show this help text`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const options = {
|
||||||
|
repo: DEFAULT_REPO,
|
||||||
|
outputDir: path.join(ROOT_FOLDER, "reports"),
|
||||||
|
limit: DEFAULT_LIMIT
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < argv.length; i += 1) {
|
||||||
|
const arg = argv[i];
|
||||||
|
|
||||||
|
if (arg === "--help" || arg === "-h") {
|
||||||
|
printUsage();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--repo") {
|
||||||
|
options.repo = argv[i + 1] ?? "";
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--output-dir") {
|
||||||
|
options.outputDir = argv[i + 1] ?? "";
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === "--limit") {
|
||||||
|
options.limit = Number.parseInt(argv[i + 1] ?? "", 10);
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown option: ${arg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.repo || !options.repo.includes("/")) {
|
||||||
|
throw new Error("--repo must be in owner/name format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isInteger(options.limit) || options.limit < 1) {
|
||||||
|
throw new Error("--limit must be a positive integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.outputDir) {
|
||||||
|
throw new Error("--output-dir is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureCommandAvailable(command) {
|
||||||
|
try {
|
||||||
|
execFileSync(command, ["--version"], {
|
||||||
|
stdio: "ignore",
|
||||||
|
timeout: DEFAULT_CMD_TIMEOUT
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Missing required command: ${command}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runGhJson(args) {
|
||||||
|
const output = execFileSync("gh", args, {
|
||||||
|
encoding: "utf8",
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
timeout: DEFAULT_CMD_TIMEOUT
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.parse(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCheckState(statusCheckRollup) {
|
||||||
|
if (!Array.isArray(statusCheckRollup) || statusCheckRollup.length === 0) {
|
||||||
|
return "NONE";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCheckRollup.some((check) => check.status !== "COMPLETED")) {
|
||||||
|
return "PENDING";
|
||||||
|
}
|
||||||
|
|
||||||
|
const failureConclusions = new Set([
|
||||||
|
"FAILURE",
|
||||||
|
"TIMED_OUT",
|
||||||
|
"ACTION_REQUIRED",
|
||||||
|
"CANCELLED",
|
||||||
|
"STALE",
|
||||||
|
"STARTUP_FAILURE"
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (statusCheckRollup.some((check) => failureConclusions.has(check.conclusion ?? ""))) {
|
||||||
|
return "FAILURE";
|
||||||
|
}
|
||||||
|
|
||||||
|
const successConclusions = new Set(["SUCCESS", "NEUTRAL", "SKIPPED"]);
|
||||||
|
const allSuccessful = statusCheckRollup.every((check) => successConclusions.has(check.conclusion ?? ""));
|
||||||
|
return allSuccessful ? "SUCCESS" : "FAILURE";
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePullRequest(pr) {
|
||||||
|
const checkState = getCheckState(pr.statusCheckRollup);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: pr.number,
|
||||||
|
title: pr.title,
|
||||||
|
author: pr.author?.login ?? "ghost",
|
||||||
|
checksPass: checkState === "SUCCESS",
|
||||||
|
checkState,
|
||||||
|
targetBranch: pr.baseRefName,
|
||||||
|
fileCount: pr.changedFiles,
|
||||||
|
createdAt: pr.createdAt,
|
||||||
|
updatedAt: pr.updatedAt,
|
||||||
|
createdAgeDays: getAgeInDays(pr.createdAt),
|
||||||
|
updatedAgeDays: getAgeInDays(pr.updatedAt),
|
||||||
|
url: pr.url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCheckLabel(pr) {
|
||||||
|
if (pr.checkState === "SUCCESS") {
|
||||||
|
return "Yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pr.checkState === "PENDING") {
|
||||||
|
return "Pending";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pr.checkState === "NONE") {
|
||||||
|
return "No checks";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "No";
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeMarkdownCell(value) {
|
||||||
|
return String(value).replaceAll("|", "\\|");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAgeInDays(timestamp) {
|
||||||
|
const milliseconds = Date.now() - new Date(timestamp).getTime();
|
||||||
|
return Math.max(0, Math.floor(milliseconds / (24 * 60 * 60 * 1000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimestampWithAge(timestamp) {
|
||||||
|
return `${timestamp.slice(0, 10)} (${getAgeInDays(timestamp)}d ago)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTable(prs) {
|
||||||
|
const lines = [
|
||||||
|
"| PR title + ID | Author | Whether checks pass | Created | Updated | Link to PR |",
|
||||||
|
"| --- | --- | --- | --- | --- | --- |"
|
||||||
|
];
|
||||||
|
|
||||||
|
if (prs.length === 0) {
|
||||||
|
lines.push("| None | - | - | - | - | - |");
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pr of prs) {
|
||||||
|
lines.push(
|
||||||
|
`| ${escapeMarkdownCell(pr.title)} (#${pr.id}) | ${escapeMarkdownCell(pr.author)} | ${getCheckLabel(pr)} | ${formatTimestampWithAge(pr.createdAt)} | ${formatTimestampWithAge(pr.updatedAt)} | [Link](${pr.url}) |`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMarkdownReport(repo, generatedAt, categorizedReports) {
|
||||||
|
const sections = [
|
||||||
|
"# Open PR report",
|
||||||
|
"",
|
||||||
|
`**Repository:** \`${repo}\` `,
|
||||||
|
`**Generated:** \`${generatedAt}\``
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const report of categorizedReports) {
|
||||||
|
sections.push("", `## ${report.heading}`, "", renderTable(report.items));
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${sections.join("\n")}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeJsonReport(filePath, items) {
|
||||||
|
fs.writeFileSync(filePath, `${JSON.stringify(items, null, 2)}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateOpenPrReport() {
|
||||||
|
const options = parseArgs(process.argv.slice(2));
|
||||||
|
|
||||||
|
ensureCommandAvailable("gh");
|
||||||
|
|
||||||
|
console.log(`Fetching open PRs from ${options.repo}...`);
|
||||||
|
|
||||||
|
const pullRequests = runGhJson([
|
||||||
|
"pr",
|
||||||
|
"list",
|
||||||
|
"--repo",
|
||||||
|
options.repo,
|
||||||
|
"--state",
|
||||||
|
"open",
|
||||||
|
"--limit",
|
||||||
|
String(options.limit),
|
||||||
|
"--json",
|
||||||
|
"number,title,url,author,baseRefName,changedFiles,createdAt,updatedAt,statusCheckRollup"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const normalizedPullRequests = pullRequests.map(normalizePullRequest);
|
||||||
|
const categorizedReports = REPORT_DEFINITIONS.map((report) => ({
|
||||||
|
...report,
|
||||||
|
items: normalizedPullRequests.filter(report.predicate)
|
||||||
|
}));
|
||||||
|
|
||||||
|
fs.mkdirSync(options.outputDir, { recursive: true });
|
||||||
|
|
||||||
|
for (const report of categorizedReports) {
|
||||||
|
writeJsonReport(path.join(options.outputDir, report.fileName), report.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
const markdownReport = renderMarkdownReport(
|
||||||
|
options.repo,
|
||||||
|
new Date().toISOString(),
|
||||||
|
categorizedReports
|
||||||
|
);
|
||||||
|
|
||||||
|
const markdownFilePath = path.join(options.outputDir, "open-pr-report.md");
|
||||||
|
fs.writeFileSync(markdownFilePath, markdownReport);
|
||||||
|
|
||||||
|
console.log(`Generated reports in ${options.outputDir}:`);
|
||||||
|
console.log(" open-pr-report.md");
|
||||||
|
for (const report of categorizedReports) {
|
||||||
|
console.log(` ${report.fileName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateOpenPrReport();
|
||||||
0
eng/generate-website-data.mjs
Normal file → Executable file
0
eng/generate-website-data.mjs
Normal file → Executable file
@@ -1,137 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { ROOT_FOLDER, SKILLS_DIR } from "./constants.mjs";
|
|
||||||
import { parseFrontmatter } from "./yaml-parser.mjs";
|
|
||||||
|
|
||||||
const PROMPTS_DIR = path.join(ROOT_FOLDER, "prompts");
|
|
||||||
/**
|
|
||||||
* Convert a prompt file to a skill folder
|
|
||||||
* @param {string} promptFilePath - Full path to the prompt file
|
|
||||||
* @returns {object} Result with success status and details
|
|
||||||
*/
|
|
||||||
function convertPromptToSkill(promptFilePath) {
|
|
||||||
const filename = path.basename(promptFilePath);
|
|
||||||
const baseName = filename.replace(".prompt.md", "");
|
|
||||||
|
|
||||||
console.log(`\nConverting: ${baseName}`);
|
|
||||||
|
|
||||||
// Parse the prompt file frontmatter
|
|
||||||
const frontmatter = parseFrontmatter(promptFilePath);
|
|
||||||
const content = fs.readFileSync(promptFilePath, "utf8");
|
|
||||||
|
|
||||||
// Extract the content after frontmatter
|
|
||||||
const frontmatterEndMatch = content.match(/^---\n[\s\S]*?\n---\n/);
|
|
||||||
const mainContent = frontmatterEndMatch
|
|
||||||
? content.substring(frontmatterEndMatch[0].length).trim()
|
|
||||||
: content.trim();
|
|
||||||
|
|
||||||
// Create skill folder
|
|
||||||
const skillFolderPath = path.join(SKILLS_DIR, baseName);
|
|
||||||
if (fs.existsSync(skillFolderPath)) {
|
|
||||||
console.log(` ⚠️ Skill folder already exists: ${baseName}`);
|
|
||||||
return { success: false, reason: "already-exists", name: baseName };
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mkdirSync(skillFolderPath, { recursive: true });
|
|
||||||
|
|
||||||
// Build new frontmatter for SKILL.md
|
|
||||||
const skillFrontmatter = {
|
|
||||||
name: baseName,
|
|
||||||
description: frontmatter?.description || `Skill converted from ${filename}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build SKILL.md content
|
|
||||||
const skillContent = `---
|
|
||||||
name: ${skillFrontmatter.name}
|
|
||||||
description: '${skillFrontmatter.description.replace(/'/g, "'''")}'
|
|
||||||
---
|
|
||||||
|
|
||||||
${mainContent}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Write SKILL.md
|
|
||||||
const skillFilePath = path.join(skillFolderPath, "SKILL.md");
|
|
||||||
fs.writeFileSync(skillFilePath, skillContent, "utf8");
|
|
||||||
|
|
||||||
console.log(` ✓ Created skill: ${baseName}`);
|
|
||||||
return { success: true, name: baseName, path: skillFolderPath };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main migration function
|
|
||||||
*/
|
|
||||||
function main() {
|
|
||||||
console.log("=".repeat(60));
|
|
||||||
console.log("Starting Prompt to Skills Migration");
|
|
||||||
console.log("=".repeat(60));
|
|
||||||
|
|
||||||
// Check if prompts directory exists
|
|
||||||
if (!fs.existsSync(PROMPTS_DIR)) {
|
|
||||||
console.error(`Error: Prompts directory not found: ${PROMPTS_DIR}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all prompt files
|
|
||||||
const promptFiles = fs
|
|
||||||
.readdirSync(PROMPTS_DIR)
|
|
||||||
.filter((file) => file.endsWith(".prompt.md"))
|
|
||||||
.map((file) => path.join(PROMPTS_DIR, file));
|
|
||||||
|
|
||||||
console.log(`Found ${promptFiles.length} prompt files to convert\n`);
|
|
||||||
|
|
||||||
const results = {
|
|
||||||
success: [],
|
|
||||||
alreadyExists: [],
|
|
||||||
failed: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert each prompt
|
|
||||||
for (const promptFile of promptFiles) {
|
|
||||||
try {
|
|
||||||
const result = convertPromptToSkill(promptFile);
|
|
||||||
if (result.success) {
|
|
||||||
results.success.push(result.name);
|
|
||||||
} else if (result.reason === "already-exists") {
|
|
||||||
results.alreadyExists.push(result.name);
|
|
||||||
} else {
|
|
||||||
results.failed.push(result.name);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const baseName = path.basename(promptFile, ".prompt.md");
|
|
||||||
console.error(` ✗ Error converting ${baseName}: ${error.message}`);
|
|
||||||
results.failed.push(baseName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print summary
|
|
||||||
console.log("\n" + "=".repeat(60));
|
|
||||||
console.log("Migration Summary");
|
|
||||||
console.log("=".repeat(60));
|
|
||||||
console.log(`✓ Successfully converted: ${results.success.length}`);
|
|
||||||
console.log(`⚠ Already existed: ${results.alreadyExists.length}`);
|
|
||||||
console.log(`✗ Failed: ${results.failed.length}`);
|
|
||||||
console.log(`Total processed: ${promptFiles.length}`);
|
|
||||||
|
|
||||||
if (results.failed.length > 0) {
|
|
||||||
console.log("\nFailed conversions:");
|
|
||||||
results.failed.forEach((name) => console.log(` - ${name}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.alreadyExists.length > 0) {
|
|
||||||
console.log("\nSkipped (already exist):");
|
|
||||||
results.alreadyExists.forEach((name) => console.log(` - ${name}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("\n✅ Migration complete!");
|
|
||||||
console.log(
|
|
||||||
"\nNext steps:\n" +
|
|
||||||
"1. Run 'npm run skill:validate' to validate all new skills\n" +
|
|
||||||
"2. Update plugin manifests to reference skills instead of commands\n" +
|
|
||||||
"3. Remove prompts directory after testing\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run migration
|
|
||||||
main();
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { PLUGINS_DIR } from "./constants.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert commands references to skills references in a plugin.json
|
|
||||||
* @param {string} pluginJsonPath - Path to the plugin.json file
|
|
||||||
* @returns {object} Result with success status and details
|
|
||||||
*/
|
|
||||||
function updatePluginManifest(pluginJsonPath) {
|
|
||||||
const pluginDir = path.dirname(path.dirname(path.dirname(pluginJsonPath)));
|
|
||||||
const pluginName = path.basename(pluginDir);
|
|
||||||
|
|
||||||
console.log(`\nProcessing plugin: ${pluginName}`);
|
|
||||||
|
|
||||||
// Read and parse plugin.json
|
|
||||||
let plugin;
|
|
||||||
try {
|
|
||||||
const content = fs.readFileSync(pluginJsonPath, "utf8");
|
|
||||||
plugin = JSON.parse(content);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(` ✗ Error reading/parsing: ${error.message}`);
|
|
||||||
return { success: false, name: pluginName, reason: "parse-error" };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if plugin has commands field
|
|
||||||
if (!plugin.commands || !Array.isArray(plugin.commands)) {
|
|
||||||
console.log(` ℹ No commands field found`);
|
|
||||||
return { success: false, name: pluginName, reason: "no-commands" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandCount = plugin.commands.length;
|
|
||||||
console.log(` Found ${commandCount} command(s) to convert`);
|
|
||||||
|
|
||||||
// Validate and convert commands to skills format
|
|
||||||
// Commands: "./commands/foo.md" → Skills: "./skills/foo/"
|
|
||||||
const validCommands = plugin.commands.filter((cmd) => {
|
|
||||||
if (typeof cmd !== "string") {
|
|
||||||
console.log(` ⚠ Skipping non-string command entry: ${JSON.stringify(cmd)}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!cmd.startsWith("./commands/") || !cmd.endsWith(".md")) {
|
|
||||||
console.log(` ⚠ Skipping command with unexpected format: ${cmd}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
const skills = validCommands.map((cmd) => {
|
|
||||||
const basename = path.basename(cmd, ".md");
|
|
||||||
return `./skills/${basename}/`;
|
|
||||||
});
|
|
||||||
// Initialize skills array if it doesn't exist or is not an array
|
|
||||||
if (!Array.isArray(plugin.skills)) {
|
|
||||||
plugin.skills = [];
|
|
||||||
}
|
|
||||||
// Add converted commands to skills array, de-duplicating entries
|
|
||||||
const allSkills = new Set(plugin.skills);
|
|
||||||
for (const skillPath of skills) {
|
|
||||||
allSkills.add(skillPath);
|
|
||||||
}
|
|
||||||
plugin.skills = Array.from(allSkills);
|
|
||||||
|
|
||||||
// Remove commands field
|
|
||||||
delete plugin.commands;
|
|
||||||
|
|
||||||
// Write updated plugin.json
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(
|
|
||||||
pluginJsonPath,
|
|
||||||
JSON.stringify(plugin, null, 2) + "\n",
|
|
||||||
"utf8"
|
|
||||||
);
|
|
||||||
console.log(` ✓ Converted ${commandCount} command(s) to skills`);
|
|
||||||
return { success: true, name: pluginName, count: commandCount };
|
|
||||||
} catch (error) {
|
|
||||||
console.log(` ✗ Error writing file: ${error.message}`);
|
|
||||||
return { success: false, name: pluginName, reason: "write-error" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main function to update all plugin manifests
|
|
||||||
*/
|
|
||||||
function main() {
|
|
||||||
console.log("=".repeat(60));
|
|
||||||
console.log("Updating Plugin Manifests: Commands → Skills");
|
|
||||||
console.log("=".repeat(60));
|
|
||||||
|
|
||||||
// Check if plugins directory exists
|
|
||||||
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
||||||
console.error(`Error: Plugins directory not found: ${PLUGINS_DIR}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all plugin.json files
|
|
||||||
const pluginDirs = fs
|
|
||||||
.readdirSync(PLUGINS_DIR, { withFileTypes: true })
|
|
||||||
.filter((entry) => entry.isDirectory())
|
|
||||||
.map((entry) => entry.name);
|
|
||||||
|
|
||||||
console.log(`Found ${pluginDirs.length} plugin directory(ies)\n`);
|
|
||||||
|
|
||||||
const results = {
|
|
||||||
updated: [],
|
|
||||||
noCommands: [],
|
|
||||||
failed: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Process each plugin
|
|
||||||
for (const dirName of pluginDirs) {
|
|
||||||
const pluginJsonPath = path.join(
|
|
||||||
PLUGINS_DIR,
|
|
||||||
dirName,
|
|
||||||
".github/plugin",
|
|
||||||
"plugin.json"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!fs.existsSync(pluginJsonPath)) {
|
|
||||||
console.log(`\nSkipping ${dirName}: no plugin.json found`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = updatePluginManifest(pluginJsonPath);
|
|
||||||
if (result.success) {
|
|
||||||
results.updated.push({ name: result.name, count: result.count });
|
|
||||||
} else if (result.reason === "no-commands") {
|
|
||||||
results.noCommands.push(result.name);
|
|
||||||
} else {
|
|
||||||
results.failed.push(result.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print summary
|
|
||||||
console.log("\n" + "=".repeat(60));
|
|
||||||
console.log("Update Summary");
|
|
||||||
console.log("=".repeat(60));
|
|
||||||
console.log(`✓ Updated plugins: ${results.updated.length}`);
|
|
||||||
console.log(`ℹ No commands field: ${results.noCommands.length}`);
|
|
||||||
console.log(`✗ Failed: ${results.failed.length}`);
|
|
||||||
console.log(`Total processed: ${pluginDirs.length}`);
|
|
||||||
|
|
||||||
if (results.updated.length > 0) {
|
|
||||||
console.log("\nUpdated plugins:");
|
|
||||||
results.updated.forEach(({ name, count }) =>
|
|
||||||
console.log(` - ${name} (${count} command(s) → skills)`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.failed.length > 0) {
|
|
||||||
console.log("\nFailed updates:");
|
|
||||||
results.failed.forEach((name) => console.log(` - ${name}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("\n✅ Plugin manifest updates complete!");
|
|
||||||
console.log(
|
|
||||||
"\nNext steps:\n" +
|
|
||||||
"1. Run 'npm run plugin:validate' to validate all updated plugins\n" +
|
|
||||||
"2. Test that plugins work correctly\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the update
|
|
||||||
main();
|
|
||||||
Reference in New Issue
Block a user