Skip to content

Commit b6ba0ec

Browse files
authored
feat(checks): add empty-body rule
Warns when a skill file has valid frontmatter but no body content. A body-less skill gives Claude nothing to act on at runtime, making the skill silently useless. Also registers the rule in the SARIF catalog.
1 parent 58461a4 commit b6ba0ec

3 files changed

Lines changed: 56 additions & 0 deletions

File tree

src/checks.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function runChecks(
3232
diagnostics.push(...checkToolsOverloaded(v));
3333
diagnostics.push(...checkDescriptionLength(v));
3434
diagnostics.push(...checkNameDrift(v));
35+
diagnostics.push(...checkEmptyBody(v));
3536
}
3637

3738
diagnostics.push(...checkCollisions(validated));
@@ -144,6 +145,20 @@ function checkNameDrift(v: ValidatedSkill): Diagnostic[] {
144145
return [];
145146
}
146147

148+
function checkEmptyBody(v: ValidatedSkill): Diagnostic[] {
149+
if (v.body.trim().length === 0) {
150+
return [
151+
{
152+
severity: "warn",
153+
rule: "empty-body",
154+
message: "skill body is empty; add instructions for Claude to follow",
155+
file: v.file,
156+
},
157+
];
158+
}
159+
return [];
160+
}
161+
147162
function tokenize(s: string): Set<string> {
148163
return new Set(
149164
s

src/sarif.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ export const RULES: readonly SarifRule[] = [
9191
helpUri: HELP_BASE,
9292
defaultLevel: "warning",
9393
},
94+
{
95+
id: "empty-body",
96+
name: "emptyBody",
97+
shortDescription:
98+
"Skill body is empty; the file has frontmatter but no instructions for Claude.",
99+
helpUri: HELP_BASE,
100+
defaultLevel: "warning",
101+
},
94102
];
95103

96104
const SEVERITY_TO_LEVEL: Record<Severity, SarifLevel> = {

test/checks.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,37 @@ describe("runChecks", () => {
201201
const d = ds.find((d) => d.rule === "tools-overloaded");
202202
expect(d?.message).toContain("11");
203203
});
204+
205+
it("warns when skill body is empty", () => {
206+
const s = mkSkill("/test/foo/foo.md", {
207+
name: "foo",
208+
description: "do the foo thing",
209+
}, "");
210+
const ds = runChecks([s], config);
211+
expect(ds.some((d) => d.rule === "empty-body")).toBe(true);
212+
});
213+
214+
it("warns when skill body is whitespace only", () => {
215+
const s = mkSkill("/test/foo/foo.md", {
216+
name: "foo",
217+
description: "do the foo thing",
218+
}, " \n\n ");
219+
const ds = runChecks([s], config);
220+
expect(ds.some((d) => d.rule === "empty-body")).toBe(true);
221+
});
222+
223+
it("does not warn on empty-body when body has content", () => {
224+
const s = mkSkill("/test/foo/foo.md", {
225+
name: "foo",
226+
description: "do the foo thing",
227+
}, "Use Read to read a file, then summarize it.");
228+
const ds = runChecks([s], config);
229+
expect(ds.find((d) => d.rule === "empty-body")).toBeUndefined();
230+
});
231+
232+
it("does not fire empty-body when frontmatter is invalid", () => {
233+
const s = mkSkill("/test/foo/foo.md", { name: "foo" }, "");
234+
const ds = runChecks([s], config);
235+
expect(ds.find((d) => d.rule === "empty-body")).toBeUndefined();
236+
});
204237
});

0 commit comments

Comments
 (0)