PoC: Add bitwarden-daily-recap plugin #274
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |