Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/skill-format-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Skill Format Check

on:
push:
branches: [main]
paths:
- "skills/**"
- "scripts/skill-format-check/**"
- ".github/workflows/skill-format-check.yml"
pull_request:
branches: [main]
paths:
- "skills/**"
- "scripts/skill-format-check/**"
- ".github/workflows/skill-format-check.yml"

permissions:
contents: read

jobs:
check-format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Run Skill Format Check
run: node scripts/skill-format-check/index.js
36 changes: 36 additions & 0 deletions scripts/skill-format-check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Skill Format Check

This directory contains a script to validate the format of `SKILL.md` files located in the `../../skills` directory.

## Purpose

The `index.js` script ensures that all `SKILL.md` files conform to the standard template defined in `skill-template/skill-template.md`. Specifically, it checks that the YAML frontmatter includes the following required fields:
- `name`
- `description`
- `metadata` (outputs a warning if missing, does not fail the build)

> **Note:** The `lark-shared` skill is explicitly excluded from these format checks.

## Usage

This script is executed automatically via GitHub Actions (`.github/workflows/skill-format-check.yml`) on pull requests and pushes that modify the `skills/` directory.

To run the check manually from the root of the repository, execute:

```bash
node scripts/skill-format-check/index.js
```

You can also specify a custom target directory as the first argument:

```bash
node scripts/skill-format-check/index.js ./path/to/my/skills
```

## Testing

This tool comes with a quick validation script to ensure it correctly identifies good and bad skill formats. To run the tests, execute:

```bash
./scripts/skill-format-check/test.sh
```
76 changes: 76 additions & 0 deletions scripts/skill-format-check/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const fs = require('fs');
const path = require('path');

// Allow passing a target directory as the first argument, default to '../../skills'
const targetDirArg = process.argv[2] || '../../skills';
const SKILLS_DIR = path.resolve(__dirname, targetDirArg);

function checkSkillFormat() {
console.log(`Checking skill format in ${SKILLS_DIR}...`);

if (!fs.existsSync(SKILLS_DIR)) {
console.error('Skills directory not found:', SKILLS_DIR);
process.exit(1);
}

const skills = fs.readdirSync(SKILLS_DIR).filter(file => {
return fs.statSync(path.join(SKILLS_DIR, file)).isDirectory();
});

let hasErrors = false;

skills.forEach(skill => {
// Skip lark-shared skill completely
if (skill === 'lark-shared') {
console.log(`⏭️ Skipping check for ${skill}`);
return;
}

const skillPath = path.join(SKILLS_DIR, skill);
const skillFile = path.join(skillPath, 'SKILL.md');

if (!fs.existsSync(skillFile)) {
console.error(`❌ [${skill}] Missing SKILL.md`);
hasErrors = true;
return;
}

const content = fs.readFileSync(skillFile, 'utf-8');

// 检查 YAML Frontmatter
if (!content.startsWith('---\n') && !content.startsWith('---\r\n')) {
console.error(`❌ [${skill}] SKILL.md must start with YAML frontmatter (---)`);
hasErrors = true;
} else {
// 兼容不同的换行符
let endOfFrontmatter = content.indexOf('\n---', 3);
if (endOfFrontmatter === -1) {
console.error(`❌ [${skill}] SKILL.md has unclosed YAML frontmatter`);
hasErrors = true;
} else {
const frontmatter = content.substring(3, endOfFrontmatter);
if (!frontmatter.includes('name:')) {
console.error(`❌ [${skill}] YAML frontmatter missing 'name'`);
hasErrors = true;
}
if (!frontmatter.includes('description:')) {
console.error(`❌ [${skill}] YAML frontmatter missing 'description'`);
hasErrors = true;
}
if (!frontmatter.includes('metadata:')) {
console.warn(`⚠️ [${skill}] YAML frontmatter missing 'metadata' (Warning only)`);
// hasErrors = true; // Downgrade to warning to not fail on existing skills
}
}
}
});

if (hasErrors) {
console.error('\n❌ Skill format check failed. Please fix the errors above.');
process.exit(1);
} else {
console.log('\n✅ Skill format check passed!');
}
}

checkSkillFormat();
63 changes: 63 additions & 0 deletions scripts/skill-format-check/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/bin/bash

# Get the directory of this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
INDEX_JS="$DIR/index.js"

echo "=== Running tests for skill-format-check ==="
echo "Index script: $INDEX_JS"

# Function to run a positive test
run_positive_test() {
local test_name=$1
echo -e "\n--- [Positive] $test_name ---"

mkdir -p "$DIR/tests/temp_test_dir"
cp -r "$DIR/tests/$test_name" "$DIR/tests/temp_test_dir/"

node "$INDEX_JS" "$DIR/tests/temp_test_dir"

if [ $? -eq 0 ]; then
echo "✅ Passed! (Correctly validated $test_name)"
rm -rf "$DIR/tests/temp_test_dir"
return 0
else
echo "❌ Failed! Expected $test_name to pass but it failed."
rm -rf "$DIR/tests/temp_test_dir"
exit 1
fi
}

# Function to run a negative test
run_negative_test() {
local test_name=$1
echo -e "\n--- [Negative] $test_name ---"

mkdir -p "$DIR/tests/temp_test_dir"
cp -r "$DIR/tests/$test_name" "$DIR/tests/temp_test_dir/"

# Run the script and suppress error output since we expect it to fail
node "$INDEX_JS" "$DIR/tests/temp_test_dir" > /dev/null 2>&1

if [ $? -eq 1 ]; then
echo "✅ Passed! (Correctly rejected $test_name)"
rm -rf "$DIR/tests/temp_test_dir"
return 0
else
echo "❌ Failed! Expected $test_name to fail but it passed."
rm -rf "$DIR/tests/temp_test_dir"
exit 1
fi
}

# Run positive tests
run_positive_test "good-skill"
run_positive_test "good-skill-minimal"
run_positive_test "good-skill-complex"

# Run negative tests
run_negative_test "bad-skill"
run_negative_test "bad-skill-no-frontmatter"
run_negative_test "bad-skill-unclosed-frontmatter"

echo -e "\n🎉 All tests passed successfully!"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# No Frontmatter Skill

This skill completely lacks a YAML frontmatter.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
name: bad-skill-unclosed
version: 1.0.0
description: "This skill has an unclosed frontmatter block."
metadata: {}

# Unclosed Frontmatter Skill

This frontmatter does not have a closing `---` block.
8 changes: 8 additions & 0 deletions scripts/skill-format-check/tests/bad-skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
version: 1.0.0
metadata: {}
---

# Bad Skill

This skill is missing required fields like name and description.
17 changes: 17 additions & 0 deletions scripts/skill-format-check/tests/good-skill-complex/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: good-skill-complex
version: 2.5.1-beta
description: >
A very complex description
that spans multiple lines
and contains weird chars: !@#$%^&*()
metadata:
requires:
bins: ["lark-cli", "node"]
cliHelp: "lark-cli something --help"
customField: "customValue"
---

# Complex Skill

This skill has a complex frontmatter block.
10 changes: 10 additions & 0 deletions scripts/skill-format-check/tests/good-skill-minimal/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: good-skill-minimal
version: 0.1.0
description: Minimal valid description
metadata: {}
---

# Minimal Skill

This has the bare minimum required fields.
12 changes: 12 additions & 0 deletions scripts/skill-format-check/tests/good-skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: good-skill
version: 1.0.0
description: "This is a properly formatted skill."
metadata:
requires:
bins: ["lark-cli"]
---

# Good Skill

This skill follows all the formatting rules.