Skip to content

docs(command-development): add SlashCommand tool coverage #27

docs(command-development): add SlashCommand tool coverage

docs(command-development): add SlashCommand tool coverage #27

name: Plugin Component Validation
on:
pull_request:
types: [opened, synchronize, ready_for_review]
paths:
- "plugins/plugin-dev/commands/**"
- "plugins/plugin-dev/skills/**"
- "plugins/plugin-dev/agents/**"
- "plugins/plugin-dev/hooks/**"
# Cancel any in-progress validation for the same PR when new commits are pushed
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
validate:
# Skip bots and draft PRs
if: |
github.actor != 'dependabot[bot]' &&
github.actor != 'claude[bot]' &&
github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Get changed plugin component files
id: changed-files
env:
GH_TOKEN: ${{ github.token }}
run: |
# Get list of changed files in this PR, filtered to plugin components
# Note: gh pr diff includes deleted files, so we filter to only existing files
changed_files=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | \
grep -E '^plugins/plugin-dev/(commands|skills|agents|hooks)/' | \
grep -v '\-SUGGESTED\.md$' | \
grep -v '\-BACKUP\.md$' | \
grep -v '\-OLD\.md$' | \
while read -r file; do [ -f "$file" ] && echo "$file"; done || true)
if [ -z "$changed_files" ]; then
echo "No plugin component files changed"
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "Changed plugin component files:"
echo "$changed_files"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
# Store as newline-separated list for the prompt
{
echo "files<<EOF"
echo "$changed_files"
echo "EOF"
} >> "$GITHUB_OUTPUT"
fi
- name: Validate plugin components
if: steps.changed-files.outputs.has_changes == 'true'
uses: anthropics/claude-code-action@6337623ebba10cf8c8214b507993f8062fd4ccfb # v1.0.22
id: validate
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
Validate ONLY the changed plugin component files listed below.
## Context
- Repository: ${{ github.repository }}
- PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}
## Changed Files to Validate
```
${{ steps.changed-files.outputs.files }}
```
**IMPORTANT**: Only validate the files listed above. Do NOT validate other files in the plugin directories.
## Instructions
1. Read and validate ONLY the changed files listed above
2. Return structured JSON with validation results
## Validation Rules
### Commands (`plugins/plugin-dev/commands/*.md`)
For each changed command file, verify:
- [ ] YAML frontmatter exists with `name`, `description`, `allowed-tools` fields
- [ ] `description` is 60 characters or fewer
- [ ] `allowed-tools` follows these rules:
- MUST use `Bash(gh:*)` not unrestricted `Bash` (security requirement)
- `Read` is always allowed (reading files is safe)
- `Write` is allowed ONLY if the command creates/exports files (e.g., status export)
- `AskUserQuestion` is always allowed (user interaction is safe)
- Do NOT flag tools that are appropriate for the command's purpose
- [ ] Body uses imperative form ("Do X", "Create Y") not second person ("You should X")
- [ ] No draft file patterns in filename (`*-SUGGESTED.md`, `*-BACKUP.md`, `*-OLD.md`)
### Skills (`plugins/plugin-dev/skills/*/SKILL.md`)
For each changed skill SKILL.md file, verify:
- [ ] YAML frontmatter exists with `name`, `description` fields
- [ ] `description` uses third-person with trigger phrases (starts with "This skill should be used when...")
- [ ] Body length is reasonable (core concepts, not exhaustive documentation)
### Agents (`plugins/plugin-dev/agents/*.md`)
For each changed agent file, verify:
- [ ] YAML frontmatter exists with `name`, `description`, `model`, `color`, `tools` fields
- [ ] `description` field includes `<example>` blocks (at least 2, ideally 3-4)
- [ ] Each `<example>` block has proper structure (Context line, `user:` and `assistant:` dialogue lines, and `<commentary>` XML tags)
- [ ] `tools` list is present and appropriate for the agent's purpose
### Hooks (`plugins/plugin-dev/hooks/hooks.json`)
For changed hooks file, verify:
- [ ] File is valid JSON
- [ ] Event types are valid: UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, PreCompact, Notification
- [ ] Matcher patterns are valid regex (test by attempting to compile)
## Output Format
Return ONLY valid JSON matching this schema:
```json
{
"commands_valid": true/false,
"commands_issues": ["issue 1", "issue 2"],
"skills_valid": true/false,
"skills_issues": ["issue 1", "issue 2"],
"agents_valid": true/false,
"agents_issues": ["issue 1", "issue 2"],
"hooks_valid": true/false,
"hooks_issues": ["issue 1", "issue 2"],
"all_valid": true/false,
"summary": "Brief overall summary"
}
```
Set `all_valid` to true only if all changed files pass validation.
Set category to valid (true) if no changed files exist for that category.
Include specific file names and line numbers in issues when possible.
claude_args: |
--allowedTools "Read,Glob,Grep"
--json-schema '{"type":"object","properties":{"commands_valid":{"type":"boolean"},"commands_issues":{"type":"array","items":{"type":"string"}},"skills_valid":{"type":"boolean"},"skills_issues":{"type":"array","items":{"type":"string"}},"agents_valid":{"type":"boolean"},"agents_issues":{"type":"array","items":{"type":"string"}},"hooks_valid":{"type":"boolean"},"hooks_issues":{"type":"array","items":{"type":"string"}},"all_valid":{"type":"boolean"},"summary":{"type":"string"}},"required":["all_valid"]}'
- name: Check validation result
if: always() && steps.changed-files.outputs.has_changes == 'true' && steps.validate.outputs.structured_output != ''
env:
VALIDATION_OUTPUT: ${{ steps.validate.outputs.structured_output }}
run: |
{
echo "## Validation Results"
echo '```json'
echo "$VALIDATION_OUTPUT"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
# Validate JSON output before processing
if ! all_valid=$(echo "$VALIDATION_OUTPUT" | jq -r '.all_valid' 2>/dev/null); then
echo "Error: Invalid JSON output from validation step"
echo "Raw output:"
echo "$VALIDATION_OUTPUT"
exit 1
fi
if [ "$all_valid" != "true" ]; then
{
echo ""
echo "### Issues Found"
# Group issues by component type for cleaner output
# Use // 0 to handle null/missing arrays gracefully
if [ "$(echo "$VALIDATION_OUTPUT" | jq -r '(.commands_issues | length) // 0')" -gt 0 ]; then
echo "**Commands:**"
echo "$VALIDATION_OUTPUT" | jq -r '.commands_issues[] | "- " + .'
fi
if [ "$(echo "$VALIDATION_OUTPUT" | jq -r '(.skills_issues | length) // 0')" -gt 0 ]; then
echo "**Skills:**"
echo "$VALIDATION_OUTPUT" | jq -r '.skills_issues[] | "- " + .'
fi
if [ "$(echo "$VALIDATION_OUTPUT" | jq -r '(.agents_issues | length) // 0')" -gt 0 ]; then
echo "**Agents:**"
echo "$VALIDATION_OUTPUT" | jq -r '.agents_issues[] | "- " + .'
fi
if [ "$(echo "$VALIDATION_OUTPUT" | jq -r '(.hooks_issues | length) // 0')" -gt 0 ]; then
echo "**Hooks:**"
echo "$VALIDATION_OUTPUT" | jq -r '.hooks_issues[] | "- " + .'
fi
} >> "$GITHUB_STEP_SUMMARY"
echo ""
echo "Component validation failed. Please fix the issues above."
exit 1
fi
{
echo ""
echo "All changed plugin components are valid."
} >> "$GITHUB_STEP_SUMMARY"
- name: Skip validation (no changes)
if: steps.changed-files.outputs.has_changes != 'true'
run: |
echo "## Validation Skipped" >> "$GITHUB_STEP_SUMMARY"
echo "No plugin component files were changed in this PR." >> "$GITHUB_STEP_SUMMARY"