name: Check Plugin Structure on: pull_request: branches: [staged] paths: - "plugins/**" permissions: contents: read pull-requests: write jobs: check-materialized-files: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Check for materialized files in plugin directories uses: actions/github-script@v7 with: script: | const { execSync } = require('child_process'); const fs = require('fs'); const path = require('path'); const pluginsDir = 'plugins'; const errors = []; if (!fs.existsSync(pluginsDir)) { console.log('No plugins directory found'); return; } const pluginDirs = fs.readdirSync(pluginsDir, { withFileTypes: true }) .filter(d => d.isDirectory()) .map(d => d.name); for (const plugin of pluginDirs) { const pluginPath = path.join(pluginsDir, plugin); // Check for materialized agent/command/skill files for (const subdir of ['agents', 'commands', 'skills']) { const subdirPath = path.join(pluginPath, subdir); if (!fs.existsSync(subdirPath)) continue; const stat = fs.lstatSync(subdirPath); if (stat.isSymbolicLink()) { errors.push(`${pluginPath}/${subdir} is a symlink — symlinks should not exist in plugin directories`); continue; } if (stat.isDirectory()) { const files = fs.readdirSync(subdirPath); if (files.length > 0) { errors.push( `${pluginPath}/${subdir}/ contains ${files.length} file(s): ${files.join(', ')}. ` + `Plugin directories on staged should only contain .github/plugin/plugin.json and README.md. ` + `Agent, command, and skill files are materialized automatically during publish to main.` ); } } } // Check for symlinks anywhere in the plugin directory try { const allFiles = execSync(`find "${pluginPath}" -type l`, { encoding: 'utf-8' }).trim(); if (allFiles) { errors.push(`${pluginPath} contains symlinks:\n${allFiles}`); } } catch (e) { // find returns non-zero if no matches, ignore } } if (errors.length > 0) { const body = [ '⚠️ **Materialized files or symlinks detected in plugin directories**', '', 'Plugin directories on the `staged` branch should only contain:', '- `.github/plugin/plugin.json` (metadata)', '- `README.md`', '', 'Agent, command, and skill files are copied in automatically when publishing to `main`.', 'Please remove the following:', '', ...errors.map(e => `- ${e}`), ].join('\n'); await github.rest.pulls.createReview({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, event: 'REQUEST_CHANGES', body }); core.setFailed('Plugin directories contain materialized files or symlinks that should not be on staged'); } else { console.log('✅ All plugin directories are clean'); }