mirror of
https://github.com/addyosmani/agent-skills.git
synced 2026-10-26 15:05:04 +00:00
fix(validation): read top-level command descriptions
Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
This commit is contained in:
2 files changed
+36
-16
No files matched your search
@@ -214,3 +214,33 @@ test('the TOML directories are not put through the YAML rules', () => {
|
||||
assert.equal(result.status, 0, result.stdout);
|
||||
assert.doesNotMatch(result.stdout, /\.gemini.*unquoted value/);
|
||||
});
|
||||
|
||||
|
||||
test('nested metadata descriptions cannot hide command description drift', () => {
|
||||
const root = makeSandbox();
|
||||
writeClaudeCommand(root, 'review', 'metadata:\n description: Review a change\ndescription: Inspect a change');
|
||||
writeTomlCommand(root, path.join('.gemini', 'commands'), 'review', 'description = "Review a change"');
|
||||
writeTomlCommand(root, 'commands', 'review', 'description = "Review a change"');
|
||||
const result = run(root);
|
||||
assert.equal(result.status, 1, result.stdout + result.stderr);
|
||||
assert.match(result.stdout, /Inspect a change/);
|
||||
});
|
||||
|
||||
test('matching top-level descriptions pass despite different nested metadata', () => {
|
||||
const root = makeSandbox();
|
||||
writeClaudeCommand(root, 'review', 'metadata:\n description: Internal metadata\ndescription: Review a change');
|
||||
writeTomlCommand(root, path.join('.gemini', 'commands'), 'review', 'description = "Review a change"');
|
||||
writeTomlCommand(root, 'commands', 'review', 'description = "Review a change"');
|
||||
const result = run(root);
|
||||
assert.equal(result.status, 0, result.stdout + result.stderr);
|
||||
});
|
||||
|
||||
test('a nested description does not supply a missing command description', () => {
|
||||
const root = makeSandbox();
|
||||
writeClaudeCommand(root, 'review', 'metadata:\n description: Review a change');
|
||||
writeTomlCommand(root, path.join('.gemini', 'commands'), 'review', 'description = "Review a change"');
|
||||
writeTomlCommand(root, 'commands', 'review', 'description = "Review a change"');
|
||||
const result = run(root);
|
||||
assert.equal(result.status, 1, result.stdout + result.stderr);
|
||||
assert.match(result.stdout, /missing or malformed description/);
|
||||
});
|
||||
@@ -25,12 +25,11 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// The same frontmatter-validity rules validate-skills applies to SKILL.md.
|
||||
// `descriptionFromMd` below splits each line on its first colon, exactly as the
|
||||
// skill reader used to, so a command whose frontmatter is not valid YAML passes
|
||||
// every check here — and Claude Code parses that frontmatter when the command is
|
||||
// loaded. The #494 thread verified all 25 SKILL.md files AND the command files by
|
||||
// hand; this makes the second half a check too.
|
||||
const { frontmatterYamlErrors } = require(path.join(__dirname, 'lib', 'skill-lint.js'));
|
||||
// Description extraction shares the top-level frontmatter reader with skills.
|
||||
// YAML validity is checked separately because Claude Code parses frontmatter
|
||||
// when the command is loaded. The #494 thread verified all 25 SKILL.md files
|
||||
// and the command files by hand; this makes the second half a check too.
|
||||
const { frontmatterYamlErrors, parseFrontmatter } = require(path.join(__dirname, 'lib', 'skill-lint.js'));
|
||||
|
||||
// ─── Config ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -55,16 +54,7 @@ const NAME_MAP_REVERSE = Object.fromEntries(
|
||||
|
||||
function descriptionFromMd(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const match = content.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n/);
|
||||
if (!match) return null;
|
||||
for (const line of match[1].split(/\r?\n/)) {
|
||||
const colonIdx = line.indexOf(':');
|
||||
if (colonIdx === -1) continue;
|
||||
if (line.slice(0, colonIdx).trim() === 'description') {
|
||||
return line.slice(colonIdx + 1).trim().replace(/^['"]|['"]$/g, '');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return parseFrontmatter(content)?.description ?? null;
|
||||
}
|
||||
|
||||
function descriptionFromToml(filePath) {
|
||||
|
||||
Reference in new issue
Block a user