Charlie KrugThe Build Log

← All posts

The skill never fired. Nothing errored. The typo was one character.

Skillcheck lints the CLAUDE.md and SKILL.md files that steer coding agents, catching the silent failures (typo'd name, shadowed slug, dead trigger) that no compiler exists to catch.

You write a skill for your coding agent, wire up the frontmatter, describe when it should fire, and then it just doesn't. No error. No warning. The agent behaves as if the file isn't there, and an hour later you find it: nmae: pdf-export instead of name:. One transposed pair of letters, and the skill never registered. Nothing told you, because nothing was watching.

That's the whole problem Skillcheck exists to solve. The files that steer agents, CLAUDE.md, AGENTS.md, and skill definitions with YAML frontmatter, have enormous say over which tools fire and which skills activate. And they have no compiler, no schema, and no linter. A required field that's missing, a name that collides with another skill, a description with no trigger language: every one of these fails silently at runtime. Skillcheck is the feedback loop the format never had.

Why "is it valid YAML" misses everything

The tempting move is to grab a YAML library, parse the frontmatter, and call it validated. That catches roughly none of the bugs that actually cost you a debugging session. name: ~ is perfectly valid YAML; it just resolves to null, so the skill has no name and never loads. nmae: is valid YAML too, a real key with a real value, and the field the runtime needs is simply absent. Two files both declaring name: research are each individually fine, and together they shadow one another so only one wins, non-deterministically. Valid YAML, broken skill, every time.

So Skillcheck models the semantic failure modes instead. Each is a small, independent rule that produces a precise diagnostic: a line number, a severity, and a concrete fix hint. Missing required field. Unterminated --- block. A name that isn't kebab-case or doesn't match its directory. A description too long for the host to keep (measured across multi-line | and > scalars, because that's where the length hides). A /skill-name mentioned in prose that resolves to nothing. And the highest-value one, name collisions, which is inherently cross-file: you cannot see it by reading either file alone, so the engine models per-file and per-project rules as first-class citizens.

The parser that keeps line numbers

Here's the design decision I like most. Skillcheck does not use a YAML library. It ships a purpose-built frontmatter parser instead, for two reasons. Skill frontmatter is a tiny subset of YAML, so a general parser is mostly dead weight. More importantly, general parsers throw away the per-key line numbers, and those line numbers are the product. A diagnostic that says "description too long" is annoying; one that says 6:14 description-too-long and drops you on the exact line is a fix. The focused parser exists to preserve position information that a real YAML library discards on the way to building its tree.

That parser has zero third-party dependencies, and so does the rest of the engine. It's plain ES-module JavaScript, which is what makes the next part real: the same source files run in Node and in the browser with no bundler. The npx skillcheck .claude/skills you drop into CI and the paste-a-file web validator call one identical engine. They can't disagree about what's valid, because the demo you see on the web page is literally the check that runs in your pipeline.

The honest edges

Skillcheck suggests fixes; it doesn't rewrite your files yet. It handles the common frontmatter shapes, not the full YAML spec (folded scalars, anchors, deep nesting are out of scope for v1), which is a deliberate bet that skill frontmatter stays simple. And the rule set is internal for now, no plugin API, though rules are data in a registry, so adding one is writing a single file and appending to an array. The roadmap is mostly "more rules," and the architecture was built to make that cheap.

Try it

Paste a SKILL.md or CLAUDE.md into the hosted validator and you get an annotated, line-referenced report in the tab, no install. Or point the CLI at a real directory: npx skillcheck .claude/skills groups results by file and exits non-zero on errors, so it drops straight into a GitHub Actions step or a pre-commit hook. Want to see it bite? Feed it a skill with name: ~, or two skills claiming the same slug, and watch it name the exact thing that would have cost you an hour.

Skillcheck is live. Free, in your browser, no signup.

This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.

Comments

Loading comments…