Skip to content

PoC: Add bitwarden-daily-recap plugin #274

PoC: Add bitwarden-daily-recap plugin

PoC: Add bitwarden-daily-recap plugin #274

name: Validate Plugins
on:
pull_request:
permissions: {}
jobs:
validate:
name: Validate plugin structure and requirements
runs-on: ubuntu-24.04
permissions:
contents: read
id-token: write
pull-requests: write
steps:
- name: Check out repo
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
fetch-depth: 0
- name: Check for relevant changes
id: relevance
env:
BASE_REF: ${{ github.base_ref }}
run: |
git fetch origin "$BASE_REF"
CHANGED=$(git diff --name-only "origin/$BASE_REF...HEAD")
# Check if any changed files match paths relevant to plugin validation
RELEVANT=$(echo "$CHANGED" | grep -E '^(plugins/|\.claude-plugin/marketplace\.json$|scripts/validate-|\.github/workflows/validate-plugins\.yml$)' || echo "")
if [[ -n "$RELEVANT" ]]; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "Relevant plugin files changed:"
echo "$RELEVANT"
else
echo "should_run=false" >> "$GITHUB_OUTPUT"
echo "No relevant plugin files changed — skipping validation."
fi
- name: Get changed plugin files
id: changed-files
if: steps.relevance.outputs.should_run == 'true'
env:
BASE_REF: ${{ github.base_ref }}
run: |
# Get list of changed files in plugins directory
git fetch origin "$BASE_REF"
CHANGED_FILES=$(git diff --name-only "origin/$BASE_REF...HEAD" | grep '^plugins/' || echo "")
# Extract unique plugin directories from changed files
CHANGED_PLUGINS=$(echo "$CHANGED_FILES" | cut -d/ -f1-2 | sort -u | tr '\n' ' ' | sed 's/ $//')
# Filter for AGENT.md, SKILL.md, and hooks.json files
# Agent files can be in two patterns:
# 1. plugins/*/agents/*/AGENT.md (subdirectory with AGENT.md)
# 2. plugins/*/agents/*.md (direct .md files in agents directory)
AGENT_FILES=$(echo "$CHANGED_FILES" | grep -E '/agents/.*\.md$' || echo "")
# Skill files are in plugins/*/skills/*/SKILL.md
SKILL_FILES=$(echo "$CHANGED_FILES" | grep '/skills/.*/SKILL\.md$' || echo "")
HOOK_FILES=$(echo "$CHANGED_FILES" | grep 'hooks\.json$' || echo "")
{
echo "changed_files<<EOF"
echo "$CHANGED_FILES"
echo "EOF"
echo "changed_plugins=$CHANGED_PLUGINS"
echo "agent_files<<EOF"
echo "$AGENT_FILES"
echo "EOF"
echo "skill_files<<EOF"
echo "$SKILL_FILES"
echo "EOF"
echo "hook_files<<EOF"
echo "$HOOK_FILES"
echo "EOF"
} >> "$GITHUB_OUTPUT"
# Check if there are any plugin component files to validate
if [[ -n "$AGENT_FILES" ]] || [[ -n "$SKILL_FILES" ]] || [[ -n "$HOOK_FILES" ]]; then
echo "has_components=true" >> "$GITHUB_OUTPUT"
else
echo "has_components=false" >> "$GITHUB_OUTPUT"
fi
- name: Validate plugin structure
id: structure
if: steps.changed-files.outputs.changed_plugins != ''
continue-on-error: true
env:
CHANGED_PLUGINS: ${{ steps.changed-files.outputs.changed_plugins }}
run: |
set -o pipefail
echo "Validating changed plugins: $CHANGED_PLUGINS"
read -ra PLUGINS_ARRAY <<< "$CHANGED_PLUGINS"
./scripts/validate-plugin-structure.sh "${PLUGINS_ARRAY[@]}" 2>&1 | tee /tmp/structure-validation.log
- name: Validate marketplace.json
id: marketplace
if: steps.changed-files.outputs.changed_plugins != ''
continue-on-error: true
env:
CHANGED_PLUGINS: ${{ steps.changed-files.outputs.changed_plugins }}
run: |
set -o pipefail
echo "Validating marketplace entries for changed plugins: $CHANGED_PLUGINS"
read -ra PLUGINS_ARRAY <<< "$CHANGED_PLUGINS"
./scripts/validate-marketplace.sh "${PLUGINS_ARRAY[@]}" 2>&1 | tee /tmp/marketplace-validation.log
- name: Validate version bump
id: version-bump
if: steps.changed-files.outputs.has_components == 'true'
continue-on-error: true
env:
BASE_REF: ${{ github.base_ref }}
CHANGED_PLUGINS: ${{ steps.changed-files.outputs.changed_plugins }}
run: |
set -o pipefail
git fetch origin "$BASE_REF"
echo "Checking version bumps for plugins with component changes: $CHANGED_PLUGINS"
read -ra PLUGINS_ARRAY <<< "$CHANGED_PLUGINS"
./scripts/validate-version-bump.sh "origin/$BASE_REF" "${PLUGINS_ARRAY[@]}" 2>&1 | tee /tmp/version-bump-validation.log
- name: Log in to Azure
if: steps.changed-files.outputs.has_components == 'true'
uses: bitwarden/gh-actions/azure-login@main
with:
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
client_id: ${{ secrets.AZURE_CLIENT_ID }}
- name: Get Azure Key Vault secrets
id: get-kv-secrets
if: steps.changed-files.outputs.has_components == 'true'
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: gh-org-bitwarden
secrets: "ANTHROPIC-CODE-REVIEW-API-KEY"
- name: Log out from Azure
if: steps.changed-files.outputs.has_components == 'true'
uses: bitwarden/gh-actions/azure-logout@main
- name: Validate plugin components and security
id: components
if: steps.changed-files.outputs.has_components == 'true'
continue-on-error: true
uses: anthropics/claude-code-action@c3d45e8e941e1b2ad7b278c57482d9c5bf1f35b3 # v1.0.99
with:
anthropic_api_key: ${{ steps.get-kv-secrets.outputs.ANTHROPIC-CODE-REVIEW-API-KEY }}
plugin_marketplaces: |
https://github.com/anthropics/claude-code.git
https://github.com/bitwarden/ai-plugins.git
plugins: |
plugin-dev@claude-code-plugins
claude-config-validator@bitwarden-marketplace
prompt: |
You have access to two plugins: plugin-dev and claude-config-validator.
Use their specialized agents to validate the changed plugin files below.
Changed files:
${{ steps.changed-files.outputs.changed_files }}
Changed plugins:
${{ steps.changed-files.outputs.changed_plugins }}
Agent files changed:
${{ steps.changed-files.outputs.agent_files }}
Skill files changed:
${{ steps.changed-files.outputs.skill_files }}
Hook files changed:
${{ steps.changed-files.outputs.hook_files }}
Perform the following validations in order:
## 1. Plugin Validation (plugin-validator agent from plugin-dev)
For each changed plugin listed above, validate the plugin structure.
This triggers the plugin-validator agent which performs comprehensive checks:
- plugin.json manifest correctness (name, version, required fields)
- Directory structure and auto-discovery compliance
- Command frontmatter (description, argument-hint, allowed-tools)
- Agent frontmatter (name 3-50 chars lowercase hyphens, description with <example> blocks, valid model/color, system prompt >20 chars)
- Hooks JSON schema, event names, and ${CLAUDE_PLUGIN_ROOT} usage in script paths
- MCP server configurations (valid types, HTTPS/WSS enforcement)
- File organization (README.md presence, no unnecessary files)
- No hardcoded credentials in any plugin files
Run this for every changed plugin directory even if only some component types changed.
## 2. Skill Review (skill-reviewer agent from plugin-dev)
If any SKILL.md files were changed (listed above), review each modified skill.
This triggers the skill-reviewer agent which evaluates:
- SKILL.md YAML frontmatter (required: name, description)
- Description quality: specific trigger phrases, third-person form, appropriate length
- Content quality: word count (target 1,000-3,000 words), imperative/infinitive writing style
- Progressive disclosure: core SKILL.md is lean, details in references/, examples in examples/, scripts in scripts/
- All referenced files actually exist
- Anti-patterns: vague triggers, bloated SKILL.md, missing examples
Skip this step if no SKILL.md files were changed.
## 3. Security Validation (claude-config-validator plugin)
For all changed plugin files, use the reviewing-claude-config skill from
the claude-config-validator plugin to perform security review:
- Scan for committed secrets (API keys, tokens, passwords)
- Check for hardcoded credentials in code
- Validate permission scoping in settings files
- Detect dangerous command auto-approvals
- Identify overly broad file access permissions
## Output Requirements
Combine all findings into a single structured report:
- Categorize issues by severity: critical, major, minor
- Include the exact file path and line for each issue
- Provide specific remediation guidance for each violation
- Distinguish errors (must fix) from warnings (should fix)
- If all checks pass, confirm with a summary of what was validated
- name: Summary
if: always()
env:
SHOULD_RUN: ${{ steps.relevance.outputs.should_run }}
STRUCTURE_RESULT: ${{ steps.structure.outcome }}
MARKETPLACE_RESULT: ${{ steps.marketplace.outcome }}
VERSION_BUMP_RESULT: ${{ steps.version-bump.outcome }}
COMPONENTS_RESULT: ${{ steps.components.outcome }}
HAS_COMPONENTS: ${{ steps.changed-files.outputs.has_components }}
CHANGED_PLUGINS: ${{ steps.changed-files.outputs.changed_plugins }}
run: |
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 Validation Summary"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [[ "$SHOULD_RUN" != "true" ]]; then
echo "✅ No plugin-related files changed — validation not required."
exit 0
fi
if [[ -n "$CHANGED_PLUGINS" ]]; then
echo "Plugin structure validation: $STRUCTURE_RESULT"
echo "Marketplace validation: $MARKETPLACE_RESULT"
else
echo "Plugin structure validation: skipped (no plugin files changed)"
echo "Marketplace validation: skipped (no plugin files changed)"
fi
if [[ "$HAS_COMPONENTS" == "true" ]]; then
echo "Version bump validation: $VERSION_BUMP_RESULT"
echo "Component & security validation (AI-driven): $COMPONENTS_RESULT"
else
echo "Version bump validation: skipped (no component files changed)"
echo "Component & security validation (AI-driven): skipped (no component files changed)"
fi
echo ""
# Display detailed results if available
if [[ -f /tmp/structure-validation.log ]] && [[ "$STRUCTURE_RESULT" == "failure" ]]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 Plugin Structure Validation Details:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
cat /tmp/structure-validation.log
echo ""
fi
if [[ -f /tmp/marketplace-validation.log ]] && [[ "$MARKETPLACE_RESULT" == "failure" ]]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 Marketplace Validation Details:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
cat /tmp/marketplace-validation.log
echo ""
fi
if [[ -f /tmp/version-bump-validation.log ]] && [[ "$VERSION_BUMP_RESULT" == "failure" ]]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 Version Bump Validation Details:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
cat /tmp/version-bump-validation.log
echo ""
fi
if [[ "$COMPONENTS_RESULT" == "failure" ]]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 Component & Security Validation Details:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "See PR comments for detailed validation feedback from:"
echo " - plugin-dev (component validation)"
echo " - claude-config-validator (security validation)"
echo ""
fi
# Check if any validation failed
FAILED=false
# Only fail on structure/marketplace if they ran and failed
if [[ -n "$CHANGED_PLUGINS" ]]; then
if [[ "$STRUCTURE_RESULT" == "failure" ]] || [[ "$MARKETPLACE_RESULT" == "failure" ]]; then
FAILED=true
fi
fi
# Only fail on version bump / components if they actually ran and failed
if [[ "$HAS_COMPONENTS" == "true" ]]; then
if [[ "$VERSION_BUMP_RESULT" == "failure" ]] || [[ "$COMPONENTS_RESULT" == "failure" ]]; then
FAILED=true
fi
fi
if [[ "$FAILED" == "true" ]]; then
echo "❌ Validation failed. Please review the errors above."
echo ""
echo "For more information on plugin requirements:"
echo " 📖 scripts/README.md"
echo " 📖 https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/README.md"
echo " 🔗 https://docs.claude.com/en/docs/claude-code/plugins-reference"
exit 1
else
echo "✅ All validation checks passed!"
echo ""
echo "For more information on plugin requirements:"
echo " 📖 scripts/README.md"
echo " 📖 https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/README.md"
echo " 🔗 https://docs.claude.com/en/docs/claude-code/plugins-reference"
fi