SkillCheck validator, Cursor exports, and per-agent installers (#27)

Three more learnings from alirezarezvani/claude-skills, applied:

1. SkillCheck validator (scripts/skillcheck.mjs) — validates every SKILL.md
   against the authoring standard (frontmatter, name/folder match, trigger +
   produces clauses, required headings) plus tier referential integrity.
   Errors fail CI; --strict fails on warnings too. New skillcheck.yml workflow
   and a SkillCheck status badge in the README. Current: 0 errors / 14 advisory
   warnings across 172 skills.

2. Cursor export platform — build-exports.mjs now generates
   exports/cursor/<bundle>/<skill>/<skill>.mdc rule files. The PLATFORMS
   registry now supports per-skill filenames (file as a function).

3. Per-agent installers — scripts/install.sh unifies install for
   claude/hermes/codex/openclaw/cursor (--link, --target, --dry-run, --list).
   Curl-able one-liners codex-install.sh, openclaw-install.sh, and
   cursor-install.sh clone the library and install in a single command.

README documents the one-line installs and Cursor exports; CHANGELOG and the
authoring standard updated.


Claude-Session: https://claude.ai/code/session_016JWn5jRD5tcEFKrubjQ6Px

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
mohitagw15856
2026-06-17 13:38:31 +01:00
committed by GitHub
parent 6886dc3b48
commit 05b6d799f0
185 changed files with 31012 additions and 19 deletions
+21 -13
View File
@@ -26,8 +26,9 @@ const pluginsDir = join(root, 'plugins');
const exportsDir = join(root, 'exports');
// ── Platform registry ───────────────────────────────────────────────────────
// To add a new platform (Gemini, Cursor, …), add an entry here. `render` gets
// To add a new platform, add an entry here. `render` gets
// { name, description, title, body, bundle } and returns the file contents.
// `file` is a fixed filename, or a function (skill) => filename for per-skill names.
const PLATFORMS = {
chatgpt: {
label: 'ChatGPT — Custom GPT instructions',
@@ -49,15 +50,16 @@ const PLATFORMS = {
render: ({ description, body }) =>
`You are a specialised assistant. ${description}\n\nFollow these instructions:\n\n${body.trim()}\n`,
},
// Example of how a future platform slots in (kept commented, not generated):
// cursor: {
// label: 'Cursor — project rule (.mdc)',
// dir: 'exports/cursor',
// file: 'rule.mdc',
// groupByBundle: false,
// render: ({ description, body }) =>
// `---\ndescription: ${JSON.stringify(description)}\nalwaysApply: false\n---\n\n${body.trim()}\n`,
// },
cursor: {
label: 'Cursor — project rule (.mdc)',
dir: 'exports/cursor',
file: (s) => `${s.name}.mdc`,
groupByBundle: true,
// Cursor reads `.cursor/rules/*.mdc`. Each rule is YAML frontmatter + the body.
// alwaysApply:false keeps it an opt-in rule the agent pulls in by description.
render: ({ description, body }) =>
`---\ndescription: ${JSON.stringify(description)}\nglobs:\nalwaysApply: false\n---\n\n${body.trim()}\n`,
},
};
// ── Helpers (shared shape with web/build-skills.mjs) ────────────────────────
@@ -115,6 +117,10 @@ function loadSkills() {
return skills;
}
// Resolve a platform's output filename for a skill (string or function).
const fileNameFor = (platform, skill) =>
typeof platform.file === 'function' ? platform.file(skill) : platform.file;
// Build the full path->content map a platform should produce.
function planPlatform(key, platform, skills) {
const files = new Map();
@@ -122,22 +128,24 @@ function planPlatform(key, platform, skills) {
for (const skill of skills) {
const parts = [base];
if (platform.groupByBundle) parts.push(skill.bundle);
parts.push(skill.name, platform.file);
parts.push(skill.name, fileNameFor(platform, skill));
files.set(join(...parts), platform.render(skill));
}
// Generated index for the platform.
const fileHint = typeof platform.file === 'function' ? '.mdc rule' : platform.file;
const index = [
`# ${platform.label}`,
'',
`> Auto-generated from \`skills/*/SKILL.md\` by \`scripts/build-exports.mjs\`.`,
`> **Do not edit these files by hand** — edit the source skill and regenerate.`,
'',
`${skills.length} skills exported. Copy a \`${platform.file}\` into the tool to use it.`,
`${skills.length} skills exported. Copy a \`${fileHint}\` into the tool to use it.`,
'',
'| Skill | Bundle | Path |',
'|---|---|---|',
...skills.map((s) => {
const rel = relative(base, [...(platform.groupByBundle ? [join(base, s.bundle)] : [base]), s.name, platform.file].reduce((a, b) => join(a, b)));
const leaf = [...(platform.groupByBundle ? [join(base, s.bundle)] : [base]), s.name, fileNameFor(platform, s)].reduce((a, b) => join(a, b));
const rel = relative(base, leaf);
return `| ${s.title} | \`${s.bundle}\` | \`${rel}\` |`;
}),
'',