diff --git a/agentic-marketplace/discover/action.yml b/agentic-marketplace/discover/action.yml index d114618..4a363f5 100644 --- a/agentic-marketplace/discover/action.yml +++ b/agentic-marketplace/discover/action.yml @@ -36,17 +36,58 @@ runs: fi echo "Discovering components..." - if ! OUTPUT=$(node "$SCRIPT_PATH" discover-all 2>&1); then + DISCOVER_STDERR=$(mktemp) + if ! OUTPUT=$(node "$SCRIPT_PATH" discover-all 2>"$DISCOVER_STDERR"); then echo "ERROR: Discovery failed" >&2 + cat "$DISCOVER_STDERR" >&2 echo "$OUTPUT" >&2 + rm -f "$DISCOVER_STDERR" exit 1 fi + # Show diagnostic messages in CI logs + if [ -s "$DISCOVER_STDERR" ]; then + cat "$DISCOVER_STDERR" + fi + rm -f "$DISCOVER_STDERR" # Output for other steps echo "components<> $GITHUB_OUTPUT echo "$OUTPUT" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT + # Write job summary + if [ -n "$GITHUB_STEP_SUMMARY" ]; then + SKILLS=$(node -e "console.log(JSON.parse(process.argv[1]).skills.length)" "$OUTPUT") + COMMANDS=$(node -e "console.log(JSON.parse(process.argv[1]).commands.length)" "$OUTPUT") + AGENTS=$(node -e "console.log(JSON.parse(process.argv[1]).agents.length)" "$OUTPUT") + SKIPPED=$(node -e "console.log(JSON.parse(process.argv[1]).skipped?.length ?? 0)" "$OUTPUT") + SKIPPED_LIST=$(node -e " + const s = JSON.parse(process.argv[1]).skipped || []; + s.forEach(p => console.log('- \`' + p + '\`')); + " "$OUTPUT") + + { + echo "### Discover" + echo "" + echo "| Component | Count |" + echo "|-----------|------:|" + echo "| Skills | $SKILLS |" + echo "| Commands | $COMMANDS |" + echo "| Agents | $AGENTS |" + if [ "$SKIPPED" -gt 0 ]; then + echo "" + echo "> [!WARNING]" + echo "> $SKIPPED file(s) skipped (no frontmatter)" + echo "" + echo "
Skipped files" + echo "" + echo "$SKIPPED_LIST" + echo "" + echo "
" + fi + } >> "$GITHUB_STEP_SUMMARY" + fi + echo "✓ Discovery complete" branding: diff --git a/agentic-marketplace/generate/action.yml b/agentic-marketplace/generate/action.yml index b468e3a..cfd7f3a 100644 --- a/agentic-marketplace/generate/action.yml +++ b/agentic-marketplace/generate/action.yml @@ -26,9 +26,64 @@ runs: exit 1 fi + MARKETPLACE_FILE=".claude-plugin/marketplace.json" + + # Save existing marketplace.json for comparison + if [ -f "$MARKETPLACE_FILE" ]; then + BEFORE=$(cat "$MARKETPLACE_FILE") + HAD_FILE=true + else + BEFORE="" + HAD_FILE=false + fi + echo "Generating marketplace files..." node "$SCRIPT_PATH" generate + # Write job summary + if [ -n "$GITHUB_STEP_SUMMARY" ]; then + if [ -f "$MARKETPLACE_FILE" ]; then + AFTER=$(cat "$MARKETPLACE_FILE") + else + AFTER="" + fi + + { + echo "### Generate" + echo "" + + if [ "$HAD_FILE" = "false" ] && [ -n "$AFTER" ]; then + echo "📄 **New file** — \`$MARKETPLACE_FILE\`" + echo "" + echo "
marketplace.json" + echo "" + echo '```json' + echo "$AFTER" + echo '```' + echo "" + echo "
" + elif [ "$BEFORE" = "$AFTER" ]; then + echo "No changes to \`$MARKETPLACE_FILE\`" + else + PLUGIN_LIST=$(node -e " + const m = JSON.parse(process.argv[1]); + m.plugins.forEach(p => console.log('- **' + p.name + '** (' + p.category + '): ' + p.description)); + " "$AFTER") + echo "📦 **Updated** \`$MARKETPLACE_FILE\`" + echo "" + echo "$PLUGIN_LIST" + echo "" + echo "
Full marketplace.json" + echo "" + echo '```json' + echo "$AFTER" + echo '```' + echo "" + echo "
" + fi + } >> "$GITHUB_STEP_SUMMARY" + fi + echo "✓ Generation complete" branding: diff --git a/agentic-marketplace/validate/action.yml b/agentic-marketplace/validate/action.yml index 14ca378..11fcf5d 100644 --- a/agentic-marketplace/validate/action.yml +++ b/agentic-marketplace/validate/action.yml @@ -42,20 +42,47 @@ runs: echo "Validating components..." - if node "$SCRIPT_PATH" validate; then + VALIDATE_OUTPUT=$(node "$SCRIPT_PATH" validate 2>&1) && VALID=true || VALID=false + + # Echo output to logs as before + echo "$VALIDATE_OUTPUT" + + if [ "$VALID" = "true" ]; then echo "valid=true" >> $GITHUB_OUTPUT echo "errors=[]" >> $GITHUB_OUTPUT echo "✓ All components valid" - exit 0 else echo "valid=false" >> $GITHUB_OUTPUT echo "✗ Validation failed" + fi - if [ "$FAIL_ON_ERROR" = "true" ]; then - exit 1 + # Write job summary + if [ -n "$GITHUB_STEP_SUMMARY" ]; then + if [ "$VALID" = "true" ]; then + ICON="✅" + STATUS="passed" else - exit 0 + ICON="❌" + STATUS="failed" fi + + { + echo "### Validate" + echo "" + echo "$ICON Validation **$STATUS**" + echo "" + echo "
Full output" + echo "" + echo '```' + echo "$VALIDATE_OUTPUT" + echo '```' + echo "" + echo "
" + } >> "$GITHUB_STEP_SUMMARY" + fi + + if [ "$VALID" = "false" ] && [ "$FAIL_ON_ERROR" = "true" ]; then + exit 1 fi branding: diff --git a/scripts/dist/discover-components.cjs b/scripts/dist/discover-components.cjs index 7993510..592e720 100755 --- a/scripts/dist/discover-components.cjs +++ b/scripts/dist/discover-components.cjs @@ -7335,7 +7335,7 @@ function loadConfig() { process.exit(1); } } - console.log("No generator.config.toml or .json found, using defaults"); + console.error("No generator.config.toml or .json found, using defaults"); return defaults; } function validateAndMergeConfig(defaults, config) { @@ -7417,6 +7417,7 @@ function discoverMarkdownComponents(rootDir, config) { const commands = []; const agents = []; const errors = []; + const skipped = []; const absoluteRoot = path.resolve(rootDir); function shouldExcludePath(relPath) { const pathParts = relPath.split(path.sep); @@ -7467,7 +7468,9 @@ function discoverMarkdownComponents(rootDir, config) { commands.push(classified.path); } else if (classified.type === "agent") { agents.push(classified.path); - } else if (!classified.skipped) { + } else if (classified.skipped) { + skipped.push(fullPath); + } else { errors.push({ path: fullPath, error: classified.error }); } } @@ -7475,7 +7478,7 @@ function discoverMarkdownComponents(rootDir, config) { } } walk(absoluteRoot, 0); - return { skills, commands, agents, errors }; + return { skills, commands, agents, errors, skipped }; } function discoverHooksFiles(rootDir, config) { const { excludeDirs, excludePatterns, maxDepth } = config.discovery; @@ -7833,7 +7836,8 @@ function discoverAllComponents(rootDir, config) { mcpServers: mcpResult.servers, hooksFiles, mcpFiles, - errors: allErrors + errors: allErrors, + skipped: mdComponents.skipped }; } function getCategoryNames(config) { diff --git a/scripts/src/discover-components.js b/scripts/src/discover-components.js index 560d316..c455a85 100644 --- a/scripts/src/discover-components.js +++ b/scripts/src/discover-components.js @@ -156,7 +156,7 @@ function loadConfig() { } // No config file found - use defaults - console.log('No generator.config.toml or .json found, using defaults'); + console.error('No generator.config.toml or .json found, using defaults'); return defaults; } @@ -293,6 +293,7 @@ function discoverMarkdownComponents(rootDir, config) { const commands = []; const agents = []; const errors = []; + const skipped = []; const absoluteRoot = path.resolve(rootDir); function shouldExcludePath(relPath) { @@ -354,7 +355,9 @@ function discoverMarkdownComponents(rootDir, config) { commands.push(classified.path); } else if (classified.type === 'agent') { agents.push(classified.path); - } else if (!classified.skipped) { + } else if (classified.skipped) { + skipped.push(fullPath); + } else { // Unclassified component with frontmatter - add to errors errors.push({ path: fullPath, error: classified.error }); } @@ -364,7 +367,7 @@ function discoverMarkdownComponents(rootDir, config) { } walk(absoluteRoot, 0); - return { skills, commands, agents, errors }; + return { skills, commands, agents, errors, skipped }; } /** @@ -1031,7 +1034,8 @@ function discoverAllComponents(rootDir, config) { mcpServers: mcpResult.servers, hooksFiles, mcpFiles, - errors: allErrors + errors: allErrors, + skipped: mdComponents.skipped }; } diff --git a/scripts/test/discover-components.test.js b/scripts/test/discover-components.test.js index ad6f01f..6230b74 100644 --- a/scripts/test/discover-components.test.js +++ b/scripts/test/discover-components.test.js @@ -12,6 +12,7 @@ const assert = require('assert'); const { loadConfig, classifyComponent, + discoverMarkdownComponents, discoverAllComponents, getCategoryNames, groupIntoPlugins, @@ -392,6 +393,59 @@ test('.md with explicit type in frontmatter takes priority', () => { }); }); +// --- skipped array --- + +console.log('\nskipped array'); + +// Full config needed for discovery functions (defaultConfig lacks excludeDirs etc.) +const discoveryConfig = { + discovery: { + skillFilename: 'SKILL.md', + commandsDir: 'commands', + agentsDir: 'agents', + excludeDirs: ['.git', 'node_modules'], + excludePatterns: [], + maxDepth: 10 + } +}; + +test('discoverMarkdownComponents returns skipped array for frontmatterless files', () => { + withTempDir((tmpDir) => { + // Create a .md file with no frontmatter (not in commands/agents/skills, not SKILL.md) + fs.writeFileSync(path.join(tmpDir, 'notes.md'), '# Just notes\n\nNo frontmatter.\n'); + // Create a valid skill for contrast + fs.writeFileSync(path.join(tmpDir, 'SKILL.md'), '---\nname: test-skill\ndescription: A test\n---\nContent\n'); + + const result = discoverMarkdownComponents(tmpDir, discoveryConfig); + assert(Array.isArray(result.skipped), 'skipped should be an array'); + assert.strictEqual(result.skipped.length, 1, 'should have 1 skipped file'); + assert(result.skipped[0].endsWith('notes.md'), 'skipped file should be notes.md'); + assert.strictEqual(result.skills.length, 1, 'should still find the skill'); + }); +}); + +test('discoverAllComponents propagates skipped array', () => { + withTempDir((tmpDir) => { + // Create a .md file with no frontmatter + fs.writeFileSync(path.join(tmpDir, 'planning.md'), '# Planning doc\n\nNo frontmatter.\n'); + + const result = discoverAllComponents(tmpDir, discoveryConfig); + assert(Array.isArray(result.skipped), 'skipped should be an array'); + assert.strictEqual(result.skipped.length, 1, 'should have 1 skipped file'); + assert(result.skipped[0].endsWith('planning.md'), 'skipped file should be planning.md'); + }); +}); + +test('discoverMarkdownComponents returns empty skipped array when all files have frontmatter', () => { + withTempDir((tmpDir) => { + fs.writeFileSync(path.join(tmpDir, 'SKILL.md'), '---\nname: test-skill\ndescription: A test\n---\nContent\n'); + + const result = discoverMarkdownComponents(tmpDir, discoveryConfig); + assert(Array.isArray(result.skipped), 'skipped should be an array'); + assert.strictEqual(result.skipped.length, 0, 'should have no skipped files'); + }); +}); + // --- Summary --- console.log(`\n${passed + failed} tests: ${passed} passed, ${failed} failed`);